<?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/fr/category/virtual-reality/</link><atom:link href="https://www.etiennepeillard.com/fr/category/virtual-reality/index.xml" rel="self" type="application/rss+xml"/><description>Virtual Reality</description><generator>Wowchemy (https://wowchemy.com)</generator><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/fr/category/virtual-reality/</link></image><item><title>TP — Interaction 3D : techniques de manipulation</title><link>https://www.etiennepeillard.com/fr/teaching/lab/3d-interaction/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/fr/teaching/lab/3d-interaction/</guid><description>&lt;section class="unity-intro" aria-label="Présentation du TP">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">RVRA · RVSI · Interaction 3D&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>Tâche expérimentale&lt;/dt>&lt;dd>Construire une tour · 2 min par essai&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Projet fourni&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>Prérequis&lt;/dt>&lt;dd>Bases Unity et VR · Casque et contrôleurs&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>Vous allez comparer des techniques de manipulation sur une tâche commune : &lt;strong>construire une tour de blocs bleus et jaunes en deux minutes&lt;/strong>. L’objectif est de comprendre comment la sélection, le point de saisie et l’interaction à distance influencent la précision, la rapidité et l’effort.&lt;/p>
&lt;p>Ce TP prolonge les initiations à Unity et à la VR. La scène et les scripts de la tâche sont fournis : votre travail porte sur la configuration des interactions et leur évaluation.&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">Fichiers du TP&lt;/span>
&lt;strong>Projet Unity 6.3 &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Scène, scripts et prefabs · ZIP · environ 79 Mo&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="RVRA_TP_3Dint_report.docx">
&lt;span class="unity-intro__card-label">Compte rendu&lt;/span>
&lt;strong>Canevas original &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Questions et résultats · DOCX en anglais&lt;/span>
&lt;/a>
&lt;/div>
&lt;div class="unity-intro__references">
&lt;span>Pour commencer&lt;/span>
&lt;a href="../intro-unity/">Les bases d’Unity 3D&lt;/a>
&lt;a href="../intro-vr/">Initiation VR&lt;/a>
&lt;a href="RVRA_TP_3DInteraction.pdf">Sujet original · PDF anglais&lt;/a>
&lt;/div>
&lt;p class="unity-intro__campus">&lt;span>IMT Atlantique · Brest&lt;/span> TP original de &lt;strong>Cédric Fleury&lt;/strong>, cours de &lt;strong>RVRA&lt;/strong>, parcours &lt;strong>RVSI&lt;/strong> de la &lt;strong>FISE&lt;/strong>.&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Plan du TP&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#la-tâche--une-tour-bicolore">La tâche : une tour bicolore&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-prendre-en-main-le-projet-et-les-interactions-de-base">1. Prendre en main le projet et les interactions de base&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#ouvrir-la-bonne-version">Ouvrir la bonne version&lt;/a>&lt;/li>
&lt;li>&lt;a href="#essayer-la-tâche">Essayer la tâche&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-préparer-le-changement-de-technique-sur-chaque-contrôleur">2. Préparer le changement de technique sur chaque contrôleur&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-modifier-le-point-de-saisie-des-blocs">3. Modifier le point de saisie des blocs&lt;/a>&lt;/li>
&lt;li>&lt;a href="#4-ajouter-un-rayon-virtuel">4. Ajouter un rayon virtuel&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#configurer-la-sélection-à-distance">Configurer la sélection à distance&lt;/a>&lt;/li>
&lt;li>&lt;a href="#comparer-les-variantes">Comparer les variantes&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#5-tester-near-far">5. Tester Near Far&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-manipuler-avec-les-deux-mains">6. Manipuler avec les deux mains&lt;/a>&lt;/li>
&lt;li>&lt;a href="#comparer-les-résultats-et-rédiger-le-compte-rendu">Comparer les résultats et rédiger le compte rendu&lt;/a>&lt;/li>
&lt;li>&lt;a href="#en-cas-de-difficulté">En cas de difficulté&lt;/a>&lt;/li>
&lt;li>&lt;a href="#auteur-et-ressources-originales">Auteur et ressources originales&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;h2 id="la-tâche--une-tour-bicolore">La tâche : une tour bicolore&lt;/h2>
&lt;p>Le générateur &lt;strong>bleu à gauche&lt;/strong> produit des blocs bleus ; le générateur &lt;strong>jaune à droite&lt;/strong> produit des blocs jaunes. Transportez-les sur la plateforme centrale pour construire la tour.&lt;/p>
&lt;ul>
&lt;li>Chaque étage comporte &lt;strong>deux blocs de même couleur&lt;/strong>.&lt;/li>
&lt;li>Les blocs d’un étage sont &lt;strong>perpendiculaires&lt;/strong> à ceux de l’étage précédent.&lt;/li>
&lt;li>La couleur alterne d’un étage au suivant : bleu, jaune, bleu…&lt;/li>
&lt;li>Un essai dure &lt;strong>2 minutes&lt;/strong>. À la fin, comptez les étages complets encore debout qui respectent ces règles.&lt;/li>
&lt;/ul>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="La scène du TP : générateurs à gauche et à droite, plateforme centrale et chronomètre." srcset="
/fr/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_5b41dd9788c322c3d4d48b24941eda25.webp 400w,
/fr/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_756a7de1c71c8ed4a83a9ff59eeec264.webp 760w,
/fr/teaching/lab/3d-interaction/images/tower-task_hu84f045da1e61d9be029ebaf330479c03_81590_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/fr/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>Scène du TP original de Cédric Fleury. Le sujet mentionne un record de 19 étages après deux essais : un repère historique, sans objectif imposé pour cette séance.&lt;/em>&lt;/p>
&lt;h2 id="1-prendre-en-main-le-projet-et-les-interactions-de-base">1. Prendre en main le projet et les interactions de base&lt;/h2>
&lt;h3 id="ouvrir-la-bonne-version">Ouvrir la bonne version&lt;/h3>
&lt;ol>
&lt;li>Téléchargez le &lt;a href="Interaction3D_Unity6000.3.zip">projet Unity 6.3&lt;/a>, décompressez-le, puis ajoutez le dossier &lt;code>Interaction3D&lt;/code> dans &lt;strong>Unity Hub &amp;gt; Add project from disk&lt;/strong>. Ouvrez le dossier qui contient &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> et &lt;code>ProjectSettings&lt;/code>.&lt;/li>
&lt;li>Le projet a été enregistré avec &lt;strong>6000.3.1f1&lt;/strong>. Utilisez la version prévue pour votre séance et conservez une copie avant toute conversion. Attendez la résolution des packages et vérifiez l’absence d’erreurs dans &lt;strong>Console&lt;/strong>.&lt;/li>
&lt;li>Ouvrez &lt;code>Assets/Scenes/Interaction.unity&lt;/code>. Repérez les deux générateurs, la plateforme, le chronomètre et les contrôleurs.&lt;/li>
&lt;li>Pour un Quest autonome, vérifiez les modules Android de Unity, &lt;strong>File &amp;gt; Build Profiles&lt;/strong>, la plateforme Android/Meta Quest, la scène à construire et les réglages XR. Suivez si nécessaire la partie déploiement du &lt;a href="../intro-vr/">TP d’initiation VR&lt;/a>. Faites &lt;strong>Build And Run&lt;/strong> sur le casque préparé pour le développement.&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">Configuration fournie&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">Conserver les versions du projet&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>Le manifeste du projet Unity 6.3 contient &lt;strong>XRI 3.3.0&lt;/strong>, &lt;strong>OpenXR 1.16.0&lt;/strong> et &lt;strong>Input System 1.17.0&lt;/strong>. Les Starter Assets déjà importés restent dans un dossier nommé &lt;strong>3.1.2&lt;/strong> : ce nom indique la version du sample lors de son import. Utilisez ces ressources existantes ; ne réimportez pas automatiquement celles d’une autre version.&lt;/p>
&lt;p>Le projet contient aussi le package Oculus. Vérifiez le fournisseur XR actif pour votre plateforme et évitez d’activer simultanément plusieurs fournisseurs pour le même casque. Le TP d’introduction VR utilise des versions légèrement différentes : reprenez sa méthode de configuration, sans remplacer aveuglément les packages ou le rig de ce projet.&lt;/p>
&lt;p>Une &lt;a href="Interaction3D_Unity2022.3.zip">archive pour Unity 2022.3.49f1&lt;/a> est également disponible pour les séances qui utilisent cet éditeur ; elle contient XRI 3.1.2. Choisissez une seule archive comme point de départ.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="essayer-la-tâche">Essayer la tâche&lt;/h3>
&lt;p>Les commandes ci-dessous sont celles du sujet fourni pour les contrôleurs Touch. Vérifiez leurs bindings dans le projet si vous utilisez un autre matériel.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Commande&lt;/th>
&lt;th>Action&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Joystick gauche&lt;/td>
&lt;td>Se déplacer dans la scène.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bouton &lt;strong>X&lt;/strong>&lt;/td>
&lt;td>Changer de mode de navigation, notamment vers la téléportation.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Contact du contrôleur droit + gâchette de l’index&lt;/td>
&lt;td>Saisir un bloc ou sélectionner un générateur / le bouton Reset.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Bouton &lt;strong>B&lt;/strong> à droite / &lt;strong>Y&lt;/strong> à gauche&lt;/td>
&lt;td>Changer de technique sur la main correspondante, une fois plusieurs modes configurés.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ol>
&lt;li>Essayez la navigation continue puis la téléportation. Choisissez une position qui permet d’atteindre les générateurs et la tour.&lt;/li>
&lt;li>Générez quelques blocs, saisissez-les, orientez-les puis relâchez-les sur la plateforme.&lt;/li>
&lt;li>Réinitialisez la scène et réalisez un premier essai chronométré. Le script fourni démarre le chronomètre à la première génération de bloc après réinitialisation.&lt;/li>
&lt;li>Notez votre premier score, puis votre meilleur score après quelques essais. Gardez le même nombre d’essais pour comparer les configurations.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>À consigner — partie 1 du compte rendu :&lt;/strong> les deux scores, le mode de navigation préféré et les raisons de ce choix pour cette tâche.&lt;/p>
&lt;h2 id="2-préparer-le-changement-de-technique-sur-chaque-contrôleur">2. Préparer le changement de technique sur chaque contrôleur&lt;/h2>
&lt;p>Le composant &lt;strong>Controller Manager&lt;/strong> (&lt;code>ControllerManager.cs&lt;/code>) permet de changer d’interactor en cours d’exécution, sans reconstruire l’application pour chaque comparaison.&lt;/p>
&lt;ol>
&lt;li>Sélectionnez le contrôleur droit, puis le gauche, et repérez &lt;strong>Controller Manager&lt;/strong> et son action &lt;strong>Switch Mode&lt;/strong>.&lt;/li>
&lt;li>Chaque mode doit être un &lt;strong>enfant direct&lt;/strong> du contrôleur et porter le tag &lt;strong>ControllerMode&lt;/strong>. Prenez le &lt;strong>Direct Interactor&lt;/strong> existant comme exemple.&lt;/li>
&lt;li>Ajoutez les prochains modes comme frères du Direct Interactor, dans l’ordre où vous voulez les tester. Vous pouvez placer un ensemble de composants sous une racine de mode portant le tag.&lt;/li>
&lt;li>Au démarrage, le script recense ces enfants, désactive les modes puis active le premier. &lt;strong>B&lt;/strong> ou &lt;strong>Y&lt;/strong> passe au suivant en désactivant les autres. Avec un seul mode, le bouton ne produit aucun changement visible.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Exemple d’organisation des modes sous le contrôleur droit : Direct, Ray, Ray Force Grab et Near Far." srcset="
/fr/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_cf757ee361caf10e6d60752b5d27e0bd.webp 400w,
/fr/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_cf41912f1bb6ad118e84f2a39c7d83bd.webp 760w,
/fr/teaching/lab/3d-interaction/images/controller-modes_hu3f23090c82a8fa253f1dd77630bdcddc_25952_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/fr/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>Exemple de hiérarchie issu du sujet original. Le nom des objets peut légèrement varier.&lt;/em>&lt;/p>
&lt;p>&lt;strong>Vérification :&lt;/strong> après avoir ajouté un second mode, contrôlez en Play qu’une seule racine de mode est active sur chaque main. Relâchez les objets avant de changer de mode. Le tag doit être placé sur l’enfant direct, car le script ne parcourt pas les petits-enfants pour établir sa liste.&lt;/p>
&lt;h2 id="3-modifier-le-point-de-saisie-des-blocs">3. Modifier le point de saisie des blocs&lt;/h2>
&lt;p>La saisie initiale ramène le bloc vers son point d’attache configuré. Comparez-la à une saisie qui conserve le point de contact.&lt;/p>
&lt;ol>
&lt;li>Dupliquez chacun des deux générateurs. Distinguez visuellement les nouvelles versions, tout en gardant une famille bleue et une famille jaune.&lt;/li>
&lt;li>Dans &lt;code>Assets/Prefabs&lt;/code>, dupliquez &lt;strong>Blue_cube&lt;/strong> et &lt;strong>Yellow_cube&lt;/strong>. Nommez les copies, par exemple, &lt;code>Blue_cube_Dynamic&lt;/code> et &lt;code>Yellow_cube_Dynamic&lt;/code>.&lt;/li>
&lt;li>Sur chaque copie, dans &lt;strong>XR Grab Interactable&lt;/strong>, activez &lt;strong>Use Dynamic Attach&lt;/strong>. Examinez &lt;strong>Match Position&lt;/strong>, &lt;strong>Match Rotation&lt;/strong> et &lt;strong>Snap To Collider Volume&lt;/strong>. Comparez leur effet, un réglage à la fois.&lt;/li>
&lt;li>Dans le composant &lt;strong>Generate Cube&lt;/strong> de chaque nouveau générateur, assignez le prefab correspondant au champ &lt;strong>Cube&lt;/strong>. Conservez la référence au chronomètre.&lt;/li>
&lt;li>Testez la saisie près du centre puis à une extrémité. Comparez le placement et l’orientation à ceux des blocs d’origine, puis refaites la tâche.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">Point d’attache et précision&lt;/h4>
&lt;div class="unity-concept__body">Le point de saisie appartient à l’&lt;strong>interactable&lt;/strong> ; le mode de sélection appartient à l’&lt;strong>interactor&lt;/strong>. Gardez la même technique et changez seulement les prefabs pour isoler l’effet du point d’attache. &lt;strong>Use Dynamic Attach&lt;/strong> permet de calculer une attache à la sélection ; ses options déterminent l’alignement obtenu.&lt;/div>
&lt;/aside>
&lt;p>&lt;strong>À consigner — partie 3 :&lt;/strong> avantages et limites de Dynamic Attach pour saisir, tourner et poser les blocs. Appuyez-vous sur des situations observées.&lt;/p>
&lt;p>Référence : &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 — documentation Unity&lt;/a>.&lt;/p>
&lt;h2 id="4-ajouter-un-rayon-virtuel">4. Ajouter un rayon virtuel&lt;/h2>
&lt;h3 id="configurer-la-sélection-à-distance">Configurer la sélection à distance&lt;/h3>
&lt;ol>
&lt;li>Ajoutez un &lt;strong>XR Ray Interactor&lt;/strong> comme enfant du contrôleur droit : clic droit dans Hierarchy, &lt;strong>XR &amp;gt; Ray Interactor&lt;/strong>. Nommez ce mode &lt;code>Ray&lt;/code> et ajoutez le tag &lt;strong>ControllerMode&lt;/strong> sur sa racine.&lt;/li>
&lt;li>Dans &lt;strong>Interaction Layer Mask&lt;/strong>, autorisez les couches des blocs et des générateurs, mais excluez la couche réservée à la téléportation (nommée &lt;code>Teleport&lt;/code> ou &lt;code>Teleport Layer&lt;/code> selon le projet).&lt;/li>
&lt;li>Dans &lt;strong>Input Configuration&lt;/strong>, configurez &lt;strong>Select Input&lt;/strong> pour la gâchette droite, en reprenant la référence d’action du Direct Interactor fourni. Ne supposez pas que le binding Select du TP précédent utilise le même bouton.&lt;/li>
&lt;li>Désactivez &lt;strong>UI Interaction&lt;/strong> pour cette activité. Vérifiez l’origine et la direction du rayon ainsi que ses composants de ligne.&lt;/li>
&lt;li>Exécutez, passez au mode Ray avec &lt;strong>B&lt;/strong>, puis testez la génération et la saisie à distance.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> le rayon sélectionne les blocs et les générateurs, sans déclencher la téléportation. Un objet doit partager une couche d’interaction XRI avec l’interactor et être détectable par son raycast physique : &lt;strong>Interaction Layer Mask&lt;/strong> et &lt;strong>Raycast Mask&lt;/strong> sont deux filtres distincts.&lt;/p>
&lt;h3 id="comparer-les-variantes">Comparer les variantes&lt;/h3>
&lt;p>Dupliquez le mode Ray sous le même contrôleur et donnez un nom explicite à chaque variante. Conservez les entrées et les filtres pour ne changer que l’option étudiée.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Variante&lt;/th>
&lt;th>Réglage à comparer&lt;/th>
&lt;th>Observation attendue&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Rayon de base&lt;/td>
&lt;td>&lt;strong>Force Grab&lt;/strong> désactivé&lt;/td>
&lt;td>Manipulation du bloc à distance.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rayon avec rappel&lt;/td>
&lt;td>&lt;strong>Force Grab&lt;/strong> activé&lt;/td>
&lt;td>Le bloc sélectionné est ramené à la main.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Attache manipulable&lt;/td>
&lt;td>&lt;strong>Manipulate Attach Transform&lt;/strong> activé&lt;/td>
&lt;td>Déplacement de l’attache au joystick ; vérifiez les entrées de translation/rotation.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Refaites un essai avec chaque variante. Examinez l’accès aux générateurs, le transport vers le centre, la rotation des blocs et la précision du relâchement. Si le joystick pilote plusieurs actions, vérifiez leurs bindings et leur concurrence avec la locomotion.&lt;/p>
&lt;p>&lt;strong>À consigner — partie 4 :&lt;/strong> bénéfices du rayon par rapport à la saisie directe, bénéfices de la saisie directe, intérêt et limites de Force Grab, utilité de Manipulate Attach Transform, configuration préférée et meilleur score obtenu.&lt;/p>
&lt;p>Référence : &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 — documentation Unity&lt;/a>.&lt;/p>
&lt;h2 id="5-tester-near-far">5. Tester Near Far&lt;/h2>
&lt;ol>
&lt;li>Dans &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Prefabs/Interactors&lt;/code>, trouvez &lt;strong>Right_NearFarInteractor&lt;/strong>. Ajoutez-le sous le contrôleur droit comme un nouveau mode.&lt;/li>
&lt;li>Placez le tag &lt;strong>ControllerMode&lt;/strong> sur sa racine. Vérifiez les références de main droite, les entrées Select et les filtres d’interaction, comme pour le rayon. Désactivez l’interaction UI pour garder les mêmes conditions.&lt;/li>
&lt;li>Testez la sélection d’un bloc proche, puis d’un bloc éloigné. Observez comment la technique combine l’interaction de proximité et à distance.&lt;/li>
&lt;li>Comparez &lt;strong>Far Attach Mode = Near&lt;/strong> et &lt;strong>Far&lt;/strong> : dans la documentation XRI 3.3, ce réglage contrôle le rappel à la main ou le maintien à distance. Le sujet original parle de l’option « Force Grab » ; repérez le réglage équivalent dans le composant installé.&lt;/li>
&lt;li>Réalisez la tâche avec les deux variantes. Créez deux modes si vous souhaitez les comparer avec B, sans nouvelle compilation.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>À consigner — partie 5 :&lt;/strong> technique vue en cours qui s’en rapproche le plus, avantages et inconvénients pour la tâche, et difficultés éventuelles lors des transitions proche/loin. Justifiez par vos essais.&lt;/p>
&lt;p>Référence : &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 — documentation Unity&lt;/a>.&lt;/p>
&lt;h2 id="6-manipuler-avec-les-deux-mains">6. Manipuler avec les deux mains&lt;/h2>
&lt;ol>
&lt;li>Ajoutez aussi des modes de manipulation au contrôleur gauche. Utilisez le prefab &lt;strong>Left_NearFarInteractor&lt;/strong> pour Near Far, ou adaptez les entrées et la latéralité d’un rayon à la main gauche.&lt;/li>
&lt;li>Vérifiez les tags et les actions. &lt;strong>Y&lt;/strong> change le mode gauche et &lt;strong>B&lt;/strong> le mode droit ; ne conservez pas les actions de la main droite sur les deux mains.&lt;/li>
&lt;li>Testez une combinaison symétrique puis une combinaison où chaque main a un rôle différent : par exemple, approvisionner d’un côté et positionner de l’autre.&lt;/li>
&lt;li>Comparez-les à votre meilleure configuration à une main, avec les mêmes règles et la même durée.&lt;/li>
&lt;/ol>
&lt;p>L’objectif est de manipuler deux blocs ou de répartir les actions entre les mains. Il n’est pas nécessaire d’activer la sélection simultanée d’un même bloc par les deux contrôleurs.&lt;/p>
&lt;p>&lt;strong>À consigner — partie 6 :&lt;/strong> bénéfices de deux techniques, combinaison la plus efficace, justification et meilleur nombre d’étages.&lt;/p>
&lt;h2 id="comparer-les-résultats-et-rédiger-le-compte-rendu">Comparer les résultats et rédiger le compte rendu&lt;/h2>
&lt;p>Utilisez le &lt;a href="RVRA_TP_3Dint_report.docx">canevas original en anglais&lt;/a> et les questions traduites dans cette page. Consultez aussi les &lt;a href="../../tutorial/rapport-tp/">consignes générales du compte rendu&lt;/a>. La numérotation des questions reprend celle du sujet : la partie 2 prépare les interactions et ne possède pas de rubrique distincte dans le canevas.&lt;/p>
&lt;p>Pour chaque configuration, gardez une trace des réglages réellement testés :&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Configuration&lt;/th>
&lt;th>Main gauche / droite&lt;/th>
&lt;th>Point de saisie&lt;/th>
&lt;th>Options&lt;/th>
&lt;th>Essai 1&lt;/th>
&lt;th>Meilleur score&lt;/th>
&lt;th>Observations&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Directe initiale&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>Initial&lt;/td>
&lt;td>Navigation choisie&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Directe dynamique&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>Dynamique&lt;/td>
&lt;td>Réglages d’attache&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rayon&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>Force Grab / attache&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>À préciser&lt;/td>
&lt;td>À préciser&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>Deux mains&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>À préciser&lt;/td>
&lt;td>Combinaison choisie&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;td>…&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Prévoyez un court entraînement comparable, réinitialisez entre les essais et notez le nombre de répétitions. Distinguez la performance mesurée du ressenti : une technique peut être rapide tout en demandant davantage d’effort. L’apprentissage peut améliorer les scores au fil de la séance ; ne concluez pas à la supériorité générale d’une technique sur la base du seul meilleur essai.&lt;/p>
&lt;p>&lt;strong>Avant de terminer :&lt;/strong> conservez la scène, les prefabs modifiés et les fichiers &lt;code>Packages&lt;/code>. Votre application doit permettre de changer les modes sans reconstruire, de générer les deux couleurs, de réinitialiser la tâche et de la réaliser avec une ou deux mains.&lt;/p>
&lt;h2 id="en-cas-de-difficulté">En cas de difficulté&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptôme&lt;/th>
&lt;th>Points à vérifier&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>B / Y ne change rien&lt;/td>
&lt;td>Plusieurs enfants directs tagués ControllerMode, action Switch Mode et ordre dans la hiérarchie.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Le rayon est visible, mais ne saisit rien&lt;/td>
&lt;td>Select Input, colliders, Interaction Layer Mask, Raycast Mask et composant interactable sur la cible.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Le rayon déclenche la téléportation&lt;/td>
&lt;td>Exclure sa couche des modes de manipulation et vérifier les entrées partagées.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Le nouveau générateur produit les anciens blocs&lt;/td>
&lt;td>Champ Cube du composant Generate Cube et prefab réellement modifié.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Near Far utilise la mauvaise main&lt;/td>
&lt;td>Prefab gauche/droit, Handedness et références d’actions.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plus de génération après la fin du temps&lt;/td>
&lt;td>Utiliser Reset avant de relancer : le chronomètre fourni exige une réinitialisation.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Le casque ne lance pas la scène&lt;/td>
&lt;td>Scène de build, plateforme, fournisseur XR et connexion ; reprendre le TP d’initiation VR.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="auteur-et-ressources-originales">Auteur et ressources originales&lt;/h2>
&lt;p>TP original conçu par &lt;strong>Cédric Fleury&lt;/strong> pour le cours de &lt;strong>RVRA&lt;/strong>, parcours &lt;strong>RVSI&lt;/strong> de la &lt;strong>FISE IMT Atlantique, campus de Brest&lt;/strong>. Contact figurant sur le sujet : &lt;a href="mailto:cedric.fleury@imt-atlantique.fr">cedric.fleury@imt-atlantique.fr&lt;/a>.&lt;/p>
&lt;p>Cette page en propose une adaptation web en français et en anglais, à partir du sujet et des ressources fournis pour l’année &lt;strong>2025–2026&lt;/strong>. Les illustrations de la tâche et des contrôleurs proviennent du sujet original.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="RVRA_TP_3DInteraction.pdf">Sujet original — PDF en anglais&lt;/a>.&lt;/li>
&lt;li>&lt;a href="RVRA_TP_3Dint_report.docx">Canevas original du compte rendu — DOCX en anglais&lt;/a>.&lt;/li>
&lt;li>&lt;a href="Interaction3D_Unity6000.3.zip">Projet Unity 6.3&lt;/a> : fichiers du projet conservés ; APK précompilé et métadonnées macOS retirés pour alléger l’archive.&lt;/li>
&lt;li>&lt;a href="Interaction3D_Unity2022.3.zip">Projet original Unity 2022.3&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>TP — Navigation 3D en réalité virtuelle</title><link>https://www.etiennepeillard.com/fr/teaching/lab/3d-navigation/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/fr/teaching/lab/3d-navigation/</guid><description>&lt;section class="unity-intro" aria-label="Présentation du TP">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">RVRA · RVSI · Navigation 3D&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>Objectif&lt;/dt>&lt;dd>Comparer les locomotions&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Environnement&lt;/dt>&lt;dd>Unity 6.3 · XRI 3.3.0&lt;br>Projet fourni · Casque VR&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Prérequis&lt;/dt>&lt;dd>Bases Unity et VR&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>Comment se déplacer efficacement dans un environnement virtuel ? Vous allez tester plusieurs &lt;strong>techniques de navigation&lt;/strong>, régler leurs paramètres et comparer leurs effets sur la recherche de cibles, l’orientation et le confort.&lt;/p>
&lt;p>Le projet propose une &lt;strong>ville&lt;/strong> et un &lt;strong>petit labyrinthe&lt;/strong> contenant des boutons rouges, les « buzzers ». Dans le labyrinthe, mesurez le temps nécessaire pour en activer &lt;strong>8&lt;/strong>. Dans la ville, activez-en le plus possible en &lt;strong>2 minutes&lt;/strong>.&lt;/p>
&lt;p>Ce TP prolonge &lt;a href="../intro-unity/">Les bases d’Unity 3D&lt;/a> et &lt;a href="../intro-vr/">Une première application de réalité virtuelle&lt;/a>. La tâche de recherche est déjà implémentée : votre travail porte sur les techniques de locomotion et leur évaluation.&lt;/p>
&lt;/div>
&lt;div class="unity-intro__links">
&lt;a class="unity-intro__card" href="#ressources-et-projet-de-départ">
&lt;span class="unity-intro__card-label">Fichiers du TP&lt;/span>
&lt;strong>Télécharger le projet &lt;span aria-hidden="true">↓&lt;/span>&lt;/strong>
&lt;span>Unity 6.3 / 2022.3 · PDF · Compte rendu&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="../intro-vr/">
&lt;span class="unity-intro__card-label">Avant de commencer&lt;/span>
&lt;strong>Une première application VR &lt;span aria-hidden="true">→&lt;/span>&lt;/strong>
&lt;span>Suivi, contrôleurs, téléportation et build casque&lt;/span>
&lt;/a>
&lt;/div>
&lt;p class="unity-intro__help">TP original : &lt;strong>Cédric Fleury&lt;/strong>, cours de &lt;strong>RVRA&lt;/strong>, parcours &lt;strong>RVSI&lt;/strong> de la &lt;strong>FISE IMT Atlantique, campus de Brest&lt;/strong>. Adaptation web bilingue à partir du sujet et des projets fournis.&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Plan du TP&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#ressources-et-projet-de-départ">Ressources et projet de départ&lt;/a>&lt;/li>
&lt;li>&lt;a href="#1-tester-la-navigation-de-base">1. Tester la navigation de base&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#ouvrir-et-exécuter-la-scène">Ouvrir et exécuter la scène&lt;/a>&lt;/li>
&lt;li>&lt;a href="#manipuler-et-mesurer">Manipuler et mesurer&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-configurer-le-déplacement-continu">2. Configurer le déplacement continu&lt;/a>&lt;/li>
&lt;li>&lt;a href="#3-basculer-entre-plusieurs-techniques">3. Basculer entre plusieurs techniques&lt;/a>&lt;/li>
&lt;li>&lt;a href="#4-ajouter-la-téléportation">4. Ajouter la téléportation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#5-ajouter-la-navigation-multi-échelle">5. Ajouter la navigation multi-échelle&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-tester-grabbing-the-air">6. Tester Grabbing-the-Air&lt;/a>&lt;/li>
&lt;li>&lt;a href="#7-bonus--world-in-miniature-et-téléportation">7. Bonus : World-in-Miniature et téléportation&lt;/a>&lt;/li>
&lt;li>&lt;a href="#compte-rendu-et-bilan">Compte rendu et bilan&lt;/a>&lt;/li>
&lt;li>&lt;a href="#dépannage-et-repères">Dépannage et repères&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;h2 id="ressources-et-projet-de-départ">Ressources et projet de départ&lt;/h2>
&lt;p>Téléchargez &lt;strong>les deux archives d’une même version&lt;/strong> et extrayez-les dans &lt;strong>un même dossier parent&lt;/strong> : leurs contenus se réunissent dans &lt;code>Navigation3D&lt;/code>. Le dossier final doit contenir &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> et &lt;code>ProjectSettings&lt;/code>, avec &lt;code>Assets/VRTemplateAssets&lt;/code>. Ajoutez ensuite &lt;strong>ce dossier &lt;code>Navigation3D&lt;/code>&lt;/strong> dans Unity Hub. Ne mélangez pas les deux versions.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Version&lt;/th>
&lt;th>Projet et scène&lt;/th>
&lt;th>Assets du template VR&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Unity 6.3 — parcours principal&lt;/strong>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity6000.3-project.zip">Projet 6000.3&lt;/a>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity6000.3-template-assets.zip">Assets 6000.3&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">Projet 2022.3&lt;/a>&lt;/td>
&lt;td>&lt;a href="resources/navigation3d-unity2022.3-template-assets.zip">Assets 2022.3&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ul>
&lt;li>&lt;a href="resources/RVRA_TP_3DNavigation.pdf">Sujet original de Cédric Fleury (PDF, en anglais)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="resources/RVRA_TP_3Dnav_report.docx">Modèle original de compte rendu (DOCX, en anglais)&lt;/a>.&lt;/li>
&lt;li>&lt;a href="../../tutorial/rapport-tp/">Consignes générales du compte rendu&lt;/a>.&lt;/li>
&lt;/ul>
&lt;p>Les téléchargements conservent les fichiers pédagogiques des projets fournis. Les builds, caches et fichiers locaux de l’éditeur ont été écartés ; les fichiers &lt;code>Assets&lt;/code>, &lt;code>Packages&lt;/code> et &lt;code>ProjectSettings&lt;/code> sont conservés sans modification.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-1">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">Conserver les versions du projet fourni&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>Le projet principal a été enregistré avec &lt;strong>Unity 6000.3.1f1&lt;/strong>, &lt;strong>XRI 3.3.0&lt;/strong>, &lt;strong>OpenXR 1.16.0&lt;/strong> et &lt;strong>Input System 1.17.0&lt;/strong>. Le projet alternatif utilise &lt;strong>Unity 2022.3.49f1&lt;/strong> et &lt;strong>XRI 3.1.2&lt;/strong>. Les samples du projet Unity 6 restent rangés sous &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2&lt;/code> : ce chemin ne désigne pas la version du package installé.&lt;/p>
&lt;p>Ouvrez le projet fourni, sans recréer un projet vierge ni mettre ses packages à jour pendant la séance. Le TP d’introduction VR utilise une configuration légèrement différente ; consultez-le pour la démarche de déploiement, puis gardez les versions et références de ce projet.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="1-tester-la-navigation-de-base">1. Tester la navigation de base&lt;/h2>
&lt;h3 id="ouvrir-et-exécuter-la-scène">Ouvrir et exécuter la scène&lt;/h3>
&lt;ol>
&lt;li>Ouvrez &lt;code>Assets/Scenes/Navigation.unity&lt;/code>. Attendez la fin de l’import et corrigez les erreurs bloquantes de &lt;strong>Console&lt;/strong> avant de construire l’application.&lt;/li>
&lt;li>Pour un casque autonome Android, vérifiez les modules Android de Unity Hub et la configuration XR de la plateforme cible. Dans &lt;strong>File &amp;gt; Build Profiles&lt;/strong> (Unity 6) ou &lt;strong>Build Settings&lt;/strong> (2022.3), sélectionnez Android ou le profil adapté au casque. Vérifiez que la scène &lt;code>Navigation&lt;/code> est cochée.&lt;/li>
&lt;li>Reprenez si nécessaire les étapes de &lt;a href="../intro-vr/">déploiement du TP d’introduction VR&lt;/a>. Sélectionnez le casque, puis lancez &lt;strong>Build And Run&lt;/strong>. Les réglages Android concernent le build autonome ; un essai PC VR dans Play nécessite une configuration et un runtime PC adaptés.&lt;/li>
&lt;li>Dans le casque, repérez le panneau de score, le chronomètre, la ville, l’entrée et la sortie du labyrinthe.&lt;/li>
&lt;/ol>
&lt;h3 id="manipuler-et-mesurer">Manipuler et mesurer&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Commande initiale&lt;/th>
&lt;th>Effet&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Joystick gauche&lt;/td>
&lt;td>Avancer, reculer, se déplacer latéralement.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Joystick droit&lt;/td>
&lt;td>Tourner, en complément de la rotation réelle de la tête.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Contrôleur droit au contact d’un buzzer + gâchette sous l’index&lt;/td>
&lt;td>Activer la cible : elle devient verte.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;ol>
&lt;li>Faites un essai libre pour comprendre la tâche et les commandes.&lt;/li>
&lt;li>&lt;strong>Labyrinthe :&lt;/strong> activez le buzzer devant le panneau de score, trouvez et activez les 8 buzzers du labyrinthe, puis notez le temps à la sortie.&lt;/li>
&lt;li>&lt;strong>Ville :&lt;/strong> démarrez une nouvelle tentative et relevez le nombre de buzzers activés en 2 minutes. Utilisez la remise à zéro du panneau entre les tentatives.&lt;/li>
&lt;li>Reportez vos résultats initiaux dans la partie 1 du compte rendu.&lt;/li>
&lt;/ol>
&lt;p>
&lt;figure >
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="Vue d’ensemble de la ville virtuelle" srcset="
/fr/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_42ded451db25207caa92544e208bcbc0.webp 400w,
/fr/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_31e2f5198cb6a429f7a97458386a4998.webp 760w,
/fr/teaching/lab/3d-navigation/images/navigation-city_hu2f28cd99afaf552a292242aefacb1ece_384843_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://www.etiennepeillard.com/fr/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>Environnements de la tâche de recherche. Illustration issue du sujet original de Cédric Fleury.&lt;/em>&lt;/p>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">Comparer les essais dans des conditions explicites&lt;/h4>
&lt;div class="unity-concept__body">Gardez les mêmes règles de départ et les mêmes critères d’arrêt. Notez le testeur, les paramètres, le nombre d’essais et les éventuels problèmes. L’apprentissage de la carte influence les scores : distinguez les essais de découverte des essais mesurés. Comparez aussi l’orientation, la précision et l’inconfort ressenti, sans réduire la qualité d’une technique au seul meilleur score.&lt;/div>
&lt;/aside>
&lt;h2 id="2-configurer-le-déplacement-continu">2. Configurer le déplacement continu&lt;/h2>
&lt;ol>
&lt;li>Dans &lt;strong>Hierarchy&lt;/strong>, dépliez &lt;strong>XR Origin&lt;/strong>, puis &lt;strong>Locomotion&lt;/strong>. Repérez les objets &lt;strong>Move&lt;/strong> et &lt;strong>Turn&lt;/strong>, leurs providers et leurs références d’actions.&lt;/li>
&lt;li>Explorez les entrées des deux composants pour choisir la répartition des commandes entre joysticks gauche et droit. Testez une modification à la fois et conservez un moyen de revenir à la configuration initiale.&lt;/li>
&lt;li>Réglez la vitesse de translation et celle de rotation. Cherchez un compromis entre déplacement rapide dans les rues, précision dans les couloirs et confort.&lt;/li>
&lt;li>Réalisez les deux tâches avec &lt;strong>la même configuration et les mêmes paramètres dans la ville et le labyrinthe&lt;/strong>.&lt;/li>
&lt;li>Dans la partie 2 du compte rendu, indiquez les actions assignées à chaque joystick, les vitesses retenues, le temps dans le labyrinthe et le score dans la ville. Justifiez vos choix.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> le déplacement et la rotation répondent séparément ; une seule commande ne doit pas déclencher deux comportements concurrents.&lt;/p>
&lt;h2 id="3-basculer-entre-plusieurs-techniques">3. Basculer entre plusieurs techniques&lt;/h2>
&lt;p>Le script fourni &lt;strong>&lt;code>NavigationModeManager&lt;/code>&lt;/strong>, sur &lt;code>Locomotion&lt;/code>, permet de changer de technique sans reconstruire l’application à chaque essai.&lt;/p>
&lt;ol>
&lt;li>Inspectez sa référence &lt;strong>Switch Mode&lt;/strong>. Le sujet prévoit le bouton &lt;strong>Y du contrôleur gauche&lt;/strong> ; vérifiez le binding effectivement utilisé sur votre matériel.&lt;/li>
&lt;li>Ouvrez le script : il parcourt les &lt;strong>enfants directs&lt;/strong> de l’objet porteur et retient ceux dont le tag est &lt;strong>&lt;code>NavigationTechnique&lt;/code>&lt;/strong>. Au démarrage, il active le premier ; chaque appui active le suivant et désactive les autres.&lt;/li>
&lt;li>Vérifiez le tag de &lt;strong>Move&lt;/strong>, puis celui des techniques à intégrer. Gardez au moins un enfant correctement tagué. L’ordre des enfants détermine l’ordre de parcours.&lt;/li>
&lt;li>Laissez le manager, les actions communes et les composants nécessaires à toutes les techniques actifs. Les objets utilitaires ne doivent pas recevoir ce tag.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> en Play ou au casque, appuyez plusieurs fois sur Y et observez les objets actifs. Une seule technique de la liste doit être active à la fois. &lt;strong>Turn&lt;/strong> peut rester commun : contrôlez ses entrées avant de réutiliser le joystick droit pour l’échelle.&lt;/p>
&lt;h2 id="4-ajouter-la-téléportation">4. Ajouter la téléportation&lt;/h2>
&lt;p>L’objet &lt;strong>Teleportation&lt;/strong> est déjà présent dans &lt;code>Locomotion&lt;/code>. Vous devez lui associer un rayon de sélection et vérifier ses liens avec les surfaces téléportables.&lt;/p>
&lt;ol>
&lt;li>Retrouvez &lt;strong>Teleport Interactor&lt;/strong> dans &lt;code>Assets/Samples/XR Interaction Toolkit/3.1.2/Starter Assets/Prefabs/Interactors&lt;/code> et ajoutez-le sous le contrôleur gauche du rig fourni.&lt;/li>
&lt;li>Dans &lt;strong>XR Ray Interactor&lt;/strong>, réglez &lt;strong>Interaction Layer Mask&lt;/strong> pour inclure &lt;strong>Teleport&lt;/strong>. Vérifiez aussi le &lt;strong>Raycast Mask&lt;/strong> et les colliders du sol. Les couches d’interaction XRI et les couches physiques sont deux filtres distincts.&lt;/li>
&lt;li>Désactivez &lt;strong>UI Interaction&lt;/strong> et &lt;strong>Manipulate Attach Transform&lt;/strong> pour cet exercice. Assignez &lt;strong>Attach Transform&lt;/strong> et &lt;strong>Ray Origin Transform&lt;/strong> aux transforms appropriés du contrôleur gauche.&lt;/li>
&lt;li>Dans &lt;strong>Input Configuration&lt;/strong>, assignez les deux entrées &lt;strong>Select&lt;/strong> à &lt;strong>XRI Left Locomotion/Teleport Mode&lt;/strong>, comme dans le sujet. Vérifiez les références dans l’asset d’actions du projet.&lt;/li>
&lt;li>Dans le script &lt;strong>Disable Interactor&lt;/strong> de l’objet &lt;code>Teleportation&lt;/code>, affectez le GameObject &lt;strong>Teleport Interactor&lt;/strong> au champ &lt;strong>Associated Interactor&lt;/strong>. Son activation suivra ainsi celle de la technique.&lt;/li>
&lt;li>Vérifiez le tag &lt;code>NavigationTechnique&lt;/code> de &lt;code>Teleportation&lt;/code>, le provider et ses références de locomotion. Vérifiez que le sol possède une surface de téléportation compatible avec la couche &lt;strong>Teleport&lt;/strong>.&lt;/li>
&lt;li>Passez à ce mode avec Y. Testez plusieurs destinations au sol, puis revenez au déplacement continu : le rayon de téléportation doit se désactiver.&lt;/li>
&lt;li>Comparez les formes de rayon et leurs paramètres de portée. Pour un rayon droit, examinez &lt;strong>Max Raycast Distance&lt;/strong> ; pour une courbe, examinez les paramètres propres à sa forme. Rejouez les deux tâches avec vos réglages retenus.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>À consigner — partie 4 :&lt;/strong> forme et portée du rayon, justification, temps dans le labyrinthe, score dans la ville, avantages et limites par rapport au déplacement continu.&lt;/p>
&lt;p>Référence : &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 — paramètres et types de ligne&lt;/a>.&lt;/p>
&lt;h2 id="5-ajouter-la-navigation-multi-échelle">5. Ajouter la navigation multi-échelle&lt;/h2>
&lt;p>L’objectif est de modifier l’échelle de l’utilisateur dans le monde pour observer autrement et parcourir de plus grandes distances. Cette commande doit rester disponible avec le déplacement continu et la téléportation.&lt;/p>
&lt;ol>
&lt;li>Créez un nouvel enfant sous &lt;code>Locomotion&lt;/code> pour le contrôle d’échelle. &lt;strong>Ne lui donnez pas le tag &lt;code>NavigationTechnique&lt;/code>&lt;/strong> : il doit rester actif quand Y change de technique.&lt;/li>
&lt;li>Ajoutez le script fourni &lt;strong>&lt;code>ContinuousScaleChangeProvider&lt;/code>&lt;/strong> et renseignez sa référence au &lt;strong>Locomotion Mediator&lt;/strong> du rig.&lt;/li>
&lt;li>Dans les Input Actions, recherchez l’action d’échelle prévue par le sujet, &lt;strong>&lt;code>XRI RightHand Locomotion/scale&lt;/code>&lt;/strong>, et contrôlez son existence et son binding. Si elle est absente de l’archive utilisée, créez une action &lt;strong>Value / Vector2&lt;/strong> liée au joystick droit et activez son action map via l’Input Action Manager. Assignez-la à &lt;strong>Right Hand Scale Action&lt;/strong>. Évitez qu’elle déclenche aussi la rotation de &lt;code>Turn&lt;/code>.&lt;/li>
&lt;li>Lisez le script : les propriétés d’actions et la lecture des joysticks sont fournies, mais &lt;code>Update()&lt;/code> contient encore les étapes &lt;strong>TODO&lt;/strong>.&lt;/li>
&lt;li>Complétez la lecture de l’entrée, puis la modification de l’échelle uniforme du &lt;strong>Transform du XR Origin&lt;/strong>. Utilisez le temps écoulé pour que la vitesse ne dépende pas de la fréquence d’affichage. Imposez des bornes strictement positives et progressives, et vérifiez la référence au mediator avant de l’utiliser.&lt;/li>
&lt;li>Testez d’abord de petites variations. Observez la hauteur, le point autour duquel l’échelle change, la portée des interactions, la vitesse apparente et les collisions.&lt;/li>
&lt;li>Ajustez le &lt;strong>Teleport Interactor&lt;/strong>, notamment sa portée et le repère utilisé par le rayon, pour conserver des destinations utilisables aux différentes échelles. Vérifiez séparément le rayon calculé et sa représentation visible.&lt;/li>
&lt;li>Ajoutez si possible une commande qui restaure l’échelle initiale, puis testez son utilité lorsque vous perdez vos repères.&lt;/li>
&lt;li>Rejouez la tâche de &lt;strong>2 minutes dans la ville&lt;/strong> avec déplacement continu + échelle, puis téléportation + échelle.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-3">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-3" class="unity-concept__title">Un changement d’échelle transforme plusieurs repères&lt;/h4>
&lt;div class="unity-concept__body">Le rig change de taille relativement au décor. La portée d’un rayon, la taille du contrôleur, les collisions et la distance parcourue ne se comportent pas nécessairement de la même façon. Identifiez ce qui doit rester constant dans le monde et ce qui doit suivre l’échelle du rig. Expliquez vos observations avant d’ajuster les paramètres.&lt;/div>
&lt;/aside>
&lt;p>&lt;strong>À consigner — partie 5 :&lt;/strong> scores des deux combinaisons, bénéfices et inconvénients du multi-échelle, technique la plus adaptée et intérêt d’une remise à l’échelle initiale. Décrivez aussi les adaptations nécessaires.&lt;/p>
&lt;h2 id="6-tester-grabbing-the-air">6. Tester Grabbing-the-Air&lt;/h2>
&lt;p>Cette technique permet de déplacer le monde virtuel par des gestes de saisie, en translation et en rotation.&lt;/p>
&lt;ol>
&lt;li>Inspectez l’objet &lt;strong>Grab Move&lt;/strong>, déjà présent dans &lt;code>Locomotion&lt;/code>, et les actions de ses composants. Vérifiez son intégration à la liste des techniques.&lt;/li>
&lt;li>Activez ce mode avec Y et identifiez les commandes de saisie. Testez le déplacement avec &lt;strong>une main&lt;/strong>, puis &lt;strong>deux mains&lt;/strong> ; observez le sens du déplacement et les effets sur l’orientation.&lt;/li>
&lt;li>Répétez les essais à plusieurs échelles, puis cherchez les &lt;strong>8 buzzers du labyrinthe&lt;/strong>. Notez le temps et les difficultés rencontrées.&lt;/li>
&lt;li>Dans la &lt;strong>ville&lt;/strong>, combinez cette technique avec le contrôle d’échelle et mesurez le score sur &lt;strong>2 minutes&lt;/strong>.&lt;/li>
&lt;li>Pour explorer le déplacement sans blocage par le sol ou les bâtiments, le sujet propose de désactiver le &lt;strong>CharacterController&lt;/strong> du XR Origin. Faites-en un essai distinct, notez ce changement dans le rapport et réactivez-le pour revenir aux conditions précédentes.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>À consigner — partie 6 :&lt;/strong> avantages, limites, précision, effort gestuel et pertinence pour la navigation multi-échelle. Appuyez vos réponses sur des situations observées dans les deux environnements.&lt;/p>
&lt;p>Référence du sujet : &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-et-téléportation">7. Bonus : World-in-Miniature et téléportation&lt;/h2>
&lt;p>Créez une représentation miniature de la ville pour faciliter la recherche des buzzers et choisir une destination à distance.&lt;/p>
&lt;ol>
&lt;li>Affichez une miniature du monde, distincte du décor à l’échelle normale. Évitez d’y dupliquer la logique du score, le rig ou les interactions de la scène principale.&lt;/li>
&lt;li>Permettez de viser un point de cette miniature avec un rayon virtuel.&lt;/li>
&lt;li>Convertissez le point sélectionné du repère de la miniature vers celui du monde, puis déplacez le rig vers une destination valide. Prenez en compte translation, rotation et échelle de la miniature.&lt;/li>
&lt;li>Vérifiez que la destination se trouve dans une zone accessible et que la miniature reste utilisable après le déplacement.&lt;/li>
&lt;li>Comparez cette aide à la recherche avec la navigation multi-échelle : repérage des cibles, orientation et précision de la destination.&lt;/li>
&lt;/ol>
&lt;p>Sources du sujet : &lt;a href="https://www.cs.cmu.edu/~stage3/publications/95/conferences/chi/paper.html" target="_blank" rel="noopener">Stoakley, Conway et 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">démonstration vidéo&lt;/a> et &lt;a href="https://github.com/AnBenLa/UnityWorldInMiniature" target="_blank" rel="noopener">exemple UnityWorldInMiniature&lt;/a>.&lt;/p>
&lt;h2 id="compte-rendu-et-bilan">Compte rendu et bilan&lt;/h2>
&lt;p>Complétez le &lt;a href="resources/RVRA_TP_3Dnav_report.docx">modèle original&lt;/a>. Il est fourni en anglais ; les questions correspondantes sont reprises en français dans cette page. Le tableau ci-dessous sert à rassembler vos résultats ; indiquez les unités, les paramètres et le nombre d’essais.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Condition&lt;/th>
&lt;th>Labyrinthe : temps pour 8 buzzers&lt;/th>
&lt;th>Ville : buzzers en 2 min&lt;/th>
&lt;th>Réglages et observations&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Navigation initiale&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>Configuration fournie&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Continu configuré&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>Mêmes paramètres dans les deux environnements&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Téléportation&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>Forme et portée du rayon&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Continu + multi-échelle&lt;/td>
&lt;td>—&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>Bornes et commande d’échelle&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Téléportation + multi-échelle&lt;/td>
&lt;td>—&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>Adaptations du rayon&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grabbing-the-Air&lt;/td>
&lt;td>À mesurer&lt;/td>
&lt;td>À mesurer avec multi-échelle&lt;/td>
&lt;td>Une/deux mains, collisions&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Concluez en reliant les performances aux caractéristiques de chaque environnement. Quelle technique choisiriez-vous pour une recherche dans la ville ? Pour un parcours dans un espace étroit ? Discutez le compromis entre efficacité, orientation et confort, ainsi que les limites de vos essais.&lt;/p>
&lt;h2 id="dépannage-et-repères">Dépannage et repères&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Problème&lt;/th>
&lt;th>Points à vérifier&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Caméra immobile au casque&lt;/td>
&lt;td>Désactiver le simulateur avant le build ; vérifier le suivi XR et le fournisseur de la plateforme cible.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Y ne change pas de technique&lt;/td>
&lt;td>Action Switch Mode active, binding du contrôleur, enfants directs tagués &lt;code>NavigationTechnique&lt;/code>, liste non vide.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rayon visible mais téléportation impossible&lt;/td>
&lt;td>Couche XRI Teleport, masque physique, collider et surface du sol, actions Select, provider et mediator.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rayon présent dans tous les modes&lt;/td>
&lt;td>Référence Associated Interactor et activation de l’objet Teleportation par le manager.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Échelle sans effet ou exception&lt;/td>
&lt;td>Script complété, mediator assigné, action Vector2 active, XR Origin ciblé.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rotation et échelle simultanées&lt;/td>
&lt;td>Actions du joystick droit utilisées à la fois par Turn et le provider d’échelle.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Changements absents au casque&lt;/td>
&lt;td>Sauvegarder la scène, reconstruire et réinstaller la bonne scène de la bonne copie du projet.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Le &lt;strong>XR Interaction Simulator&lt;/strong> fourni permet de vérifier la logique dans l’éditeur : activez-le pour un essai sans casque et consultez son aide de commandes. Désactivez-le avant les essais casque et le build. La performance, l’échelle perçue et le confort doivent être évalués au casque. Faites des pauses et interrompez un essai en cas d’inconfort.&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">locomotion XRI 3.3&lt;/a> pour le projet Unity 6 ; &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.1/manual/index.html" target="_blank" rel="noopener">manuel XRI 3.1&lt;/a> pour l’alternative 2022.3.&lt;/p>
&lt;p>&lt;strong>Crédits :&lt;/strong> sujet, tâche de recherche et projets de départ de &lt;strong>Cédric Fleury&lt;/strong>, cours de &lt;strong>RVRA&lt;/strong>, parcours &lt;strong>RVSI&lt;/strong>, &lt;strong>FISE IMT Atlantique — campus de Brest&lt;/strong>. Les noms des scripts et des actions sont conservés pour faciliter le lien avec les ressources originales.&lt;/p></description></item><item><title>TP — Première application de réalité virtuelle</title><link>https://www.etiennepeillard.com/fr/teaching/lab/intro-vr/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/fr/teaching/lab/intro-vr/</guid><description>&lt;section class="unity-intro" aria-label="Présentation du TP">
&lt;div class="unity-intro__top">
&lt;span class="unity-intro__eyebrow">Premiers pas · Réalité virtuelle&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>Durée indicative&lt;/dt>&lt;dd>3 heures&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Environnement&lt;/dt>&lt;dd>Unity 6.3 LTS · URP&lt;br>OpenXR · XRI 3.3.2&lt;/dd>&lt;/div>
&lt;div>&lt;dt>Niveau&lt;/dt>&lt;dd>Débutant VR · Bases Unity&lt;/dd>&lt;/div>
&lt;/dl>
&lt;div class="unity-intro__body">&lt;p>Vous allez réaliser un petit &lt;strong>atelier de cubes en VR&lt;/strong> : regarder autour de vous, vous déplacer par téléportation, saisir et lancer des objets, faire apparaître un cube avec un bouton du contrôleur et changer la couleur d’un objet en le visant.&lt;/p>
&lt;p>Ce TP se place généralement après &lt;a href="../intro-unity/">Les bases d’Unity 3D&lt;/a>. Vous devez savoir créer une scène, régler un Transform, ajouter un composant, fabriquer un prefab et attacher un script C#. Si vous avez déjà ces bases, vous pouvez commencer directement ici.&lt;/p>
&lt;p>Comptez environ 3 heures, installations et premiers builds compris ; adaptez ce temps au matériel disponible.&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">Avant de commencer&lt;/span>
&lt;strong>Les bases d’Unity 3D &lt;span aria-hidden="true">→&lt;/span>&lt;/strong>
&lt;span>Scènes, composants, prefabs et premiers scripts C#&lt;/span>
&lt;/a>
&lt;a class="unity-intro__card" href="../../tutorial/rapport-tp/">
&lt;span class="unity-intro__card-label">À consulter&lt;/span>
&lt;strong>Consignes du compte rendu &lt;span aria-hidden="true">↗&lt;/span>&lt;/strong>
&lt;span>Attendus et canevas du rapport&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/">Manuel Unity 6.3&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 uniquement&lt;/span> &lt;a href="../../tutorial/unity-instructions-pc-ecole/">Configurer les PC de l’école &lt;span aria-hidden="true">→&lt;/span>&lt;/a>&lt;/p>
&lt;details class="unity-intro__outline">
&lt;summary>Plan du TP&lt;/summary>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#1-préparer-le-projet-et-les-packages">1. Préparer le projet et les packages&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#créer-un-atelier-simple">Créer un atelier simple&lt;/a>&lt;/li>
&lt;li>&lt;a href="#installer-la-pile-xr">Installer la pile XR&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#2-installer-le-point-de-vue-et-les-contrôleurs">2. Installer le point de vue et les contrôleurs&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#premier-test-sans-casque">Premier test sans casque&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#3-exécuter-une-première-fois-sur-le-casque">3. Exécuter une première fois sur le casque&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#meta-quest-autonome--construire-et-installer-un-apk">Meta Quest autonome : construire et installer un APK&lt;/a>&lt;/li>
&lt;li>&lt;a href="#casque-pc--tester-directement-dans-léditeur">Casque PC : tester directement dans l’éditeur&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#4-se-déplacer-par-téléportation">4. Se déplacer par téléportation&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#comparer-les-modes-de-navigation">Comparer les modes de navigation&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#5-saisir-et-lancer-les-cubes">5. Saisir et lancer les cubes&lt;/a>&lt;/li>
&lt;li>&lt;a href="#6-créer-un-cube-avec-une-action-du-contrôleur">6. Créer un cube avec une action du contrôleur&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#définir-laction">Définir l’action&lt;/a>&lt;/li>
&lt;li>&lt;a href="#écrire-et-brancher-objectspawn">Écrire et brancher ObjectSpawn&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#7-réagir-au-survol-et-à-la-sélection-dun-objet">7. Réagir au survol et à la sélection d’un objet&lt;/a>&lt;/li>
&lt;li>&lt;a href="#8-vérifier-lapplication-et-rendre-compte">8. Vérifier l’application et rendre compte&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#pour-aller-plus-loin">Pour aller plus loin&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#en-cas-de-blocage">En cas de blocage&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#pour-poursuivre">Pour poursuivre&lt;/a>&lt;/li>
&lt;li>&lt;a href="#documentation-de-référence">Documentation de référence&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/details>
&lt;/section>
&lt;p>&lt;strong>Pour poursuivre :&lt;/strong> le &lt;a href="../3d-navigation/">TP Navigation 3D&lt;/a> compare déplacement continu, téléportation, navigation multi-échelle et Grabbing-the-Air dans une ville et un labyrinthe.&lt;/p>
&lt;h2 id="1-préparer-le-projet-et-les-packages">1. Préparer le projet et les packages&lt;/h2>
&lt;h3 id="créer-un-atelier-simple">Créer un atelier simple&lt;/h3>
&lt;ol>
&lt;li>Dans &lt;strong>Unity Hub&lt;/strong>, créez un projet &lt;strong>Universal 3D&lt;/strong> avec &lt;strong>Unity 6.3 LTS (6000.3)&lt;/strong>. Nommez le projet &lt;code>VRBasics&lt;/code>.&lt;/li>
&lt;li>Dans &lt;code>Assets&lt;/code>, créez les dossiers &lt;code>Scenes&lt;/code>, &lt;code>Materials&lt;/code>, &lt;code>Prefabs&lt;/code> et &lt;code>Scripts&lt;/code>. Enregistrez la scène sous &lt;code>Assets/Scenes/VRBasics.unity&lt;/code>.&lt;/li>
&lt;li>Ajoutez un &lt;strong>Plane&lt;/strong> &lt;code>Ground&lt;/code>, position &lt;code>(0, 0, 0)&lt;/code>, échelle &lt;code>(2, 1, 2)&lt;/code> : il mesure 20 × 20 mètres. Gardez son collider.&lt;/li>
&lt;li>Ajoutez une table avec un &lt;strong>Cube&lt;/strong> &lt;code>Table&lt;/code>, position &lt;code>(0, 0.75, 2)&lt;/code>, échelle &lt;code>(1.5, 0.1, 0.8)&lt;/code>. Gardez son Box Collider ; aucun Rigidbody n’est nécessaire pour cette table fixe.&lt;/li>
&lt;li>Ajoutez trois cubes de côté &lt;code>0.15&lt;/code>, espacés sur la table, avec Y &lt;code>0.9&lt;/code>. Ajoutez à chacun un &lt;strong>Rigidbody&lt;/strong>, &lt;strong>Use Gravity&lt;/strong> activé et &lt;strong>Is Kinematic&lt;/strong> désactivé. Créez des matériaux &lt;strong>Universal Render Pipeline/Lit&lt;/strong> de couleurs différentes et assignez-les. Ici, &lt;strong>1 unité Unity représente 1 mètre&lt;/strong>.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> en Play, les cubes tombent légèrement et reposent sur la table. Arrêtez Play avant de poursuivre.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-1">
&lt;p class="unity-concept__label">Après le TP Unity&lt;/p>
&lt;h4 id="unity-concept-1" class="unity-concept__title">Réutiliser votre scène est possible&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>Travaillez dans une &lt;strong>copie du projet&lt;/strong>, puis enregistrez une nouvelle scène &lt;code>VRBasics&lt;/code>. Conservez le sol, les matériaux et quelques objets. Désactivez le joueur FPS entier, sa caméra, sa caméra virtuelle Cinemachine et les scripts de clic ou de création au clavier/souris. Retirez les scripts de rotation sur les objets à saisir. Le rig XR apportera sa propre caméra et ses propres entrées.&lt;/p>
&lt;p>Réduisez quelques cubes à une taille manipulable et ajoutez la table ci-dessus. Inutile de réimporter la voiture ou le contrôleur FPS. Si votre scène est difficile à isoler, repartez du petit atelier proposé ici.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="installer-la-pile-xr">Installer la pile XR&lt;/h3>
&lt;p>Ouvrez &lt;strong>Window &amp;gt; Package Management &amp;gt; Package Manager&lt;/strong>, puis &lt;strong>Unity Registry&lt;/strong>. Utilisez les versions stables suivantes comme référence de séance ; évitez les versions &lt;code>pre&lt;/code> et ne mettez pas les packages à jour pendant le TP.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Package&lt;/th>
&lt;th>Identifiant&lt;/th>
&lt;th>Rôle / référence&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 et bindings ; &lt;strong>1.20.0&lt;/strong>, comme dans le TP Unity.&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>Activation des fournisseurs XR par plateforme ; version compatible résolue par Unity.&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 avec le système XR ; &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>Interactions et locomotion ; &lt;strong>3.3.2&lt;/strong>.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Les pages Unity 6.3 donnent &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> et &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> comme versions de référence publiées pour cet éditeur. Des branches plus récentes existent ; ce TP fixe une base commune pour retrouver les mêmes composants et samples.&lt;/p>
&lt;ol>
&lt;li>Installez les packages absents. Si nécessaire, utilisez &lt;strong>Install package by name&lt;/strong> avec l’identifiant et la version indiqués. Laissez Unity installer leurs dépendances.&lt;/li>
&lt;li>Dans &lt;strong>Edit &amp;gt; Project Settings &amp;gt; Player &amp;gt; Other Settings &amp;gt; Configuration&lt;/strong>, vérifiez &lt;strong>Active Input Handling = Input System Package (New)&lt;/strong>. Acceptez le redémarrage s’il est demandé.&lt;/li>
&lt;li>Dans la fiche de &lt;strong>XR Interaction Toolkit &amp;gt; Samples&lt;/strong>, importez &lt;strong>Starter Assets&lt;/strong> et &lt;strong>XR Interaction Simulator&lt;/strong>. Choisissez bien ce dernier : &lt;strong>XR Device Simulator&lt;/strong> est un autre sample, correspondant à l’ancien simulateur.&lt;/li>
&lt;li>Ouvrez &lt;strong>Edit &amp;gt; Project Settings &amp;gt; XR Plug-in Management &amp;gt; Project Validation&lt;/strong>. Sélectionnez la plateforme à vérifier, lisez les problèmes signalés et appliquez les corrections correspondant aux packages et samples installés. Si une règle demande de créer une couche d’interaction &lt;strong>Teleport&lt;/strong>, appliquez-la. Revenez dans cette fenêtre après le choix de la plateforme casque.&lt;/li>
&lt;li>Notez les versions effectivement installées. &lt;code>Packages/manifest.json&lt;/code> et &lt;code>Packages/packages-lock.json&lt;/code> doivent être conservés avec le projet pour retrouver cette configuration.&lt;/li>
&lt;/ol>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-2">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-2" class="unity-concept__title">Qui fait quoi dans la pile XR ?&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>&lt;strong>OpenXR&lt;/strong> relie l’application au runtime du casque. &lt;strong>Input System&lt;/strong> expose les poses et commandes sous forme d’actions. &lt;strong>XRI&lt;/strong> utilise ces entrées pour sélectionner, saisir ou déplacer le joueur.&lt;/p>
&lt;p>Le &lt;strong>runtime OpenXR&lt;/strong> est le logiciel qui fournit la session XR sur la machine d’exécution. Sur un Quest autonome, il s’exécute dans le casque ; pour tester un casque PC dans l’éditeur, il doit être installé et actif sur le PC.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="2-installer-le-point-de-vue-et-les-contrôleurs">2. Installer le point de vue et les contrôleurs&lt;/h2>
&lt;ol>
&lt;li>Supprimez la &lt;strong>Main Camera&lt;/strong> initiale, ou désactivez son GameObject. Gardez la lumière et le sol.&lt;/li>
&lt;li>Dans &lt;strong>Project&lt;/strong>, ouvrez &lt;code>Assets/Samples/XR Interaction Toolkit/3.3.2/Starter Assets/Prefabs&lt;/code>. Glissez &lt;strong>XR Origin (XR Rig)&lt;/strong> dans Hierarchy.&lt;/li>
&lt;li>Réglez le Transform racine du rig sur position &lt;code>(0, 0, 0)&lt;/code>, rotation &lt;code>(0, 0, 0)&lt;/code> et échelle &lt;code>(1, 1, 1)&lt;/code>. Dans son composant &lt;strong>XR Origin&lt;/strong>, choisissez &lt;strong>Requested Tracking Origin Mode = Floor&lt;/strong> pour un casque utilisant le suivi du sol. Vérifiez que &lt;strong>Camera&lt;/strong> référence la caméra enfant du rig.&lt;/li>
&lt;li>Ajoutez &lt;strong>GameObject &amp;gt; XR &amp;gt; XR Interaction Manager&lt;/strong> si la scène n’en contient pas déjà un. Gardez un seul manager pour ce TP. Dans les interactables créés ensuite, vous pourrez le glisser dans &lt;strong>Interaction Manager&lt;/strong>.&lt;/li>
&lt;li>Dépliez le rig et repérez la caméra, les contrôleurs gauche et droit, leurs interactors et le groupe &lt;strong>Locomotion&lt;/strong>. Vérifiez que l’&lt;strong>Input Action Manager&lt;/strong> du rig référence &lt;strong>XRI Default Input Actions&lt;/strong>, fourni par les Starter Assets. Gardez les références d’actions du prefab.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Repères à retrouver dans Hierarchy&lt;/strong> — les noms des enfants peuvent varier légèrement :&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">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-3" class="unity-concept__title">Déplacer le joueur ou suivre sa tête ?&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>La caméra enfant reçoit la &lt;strong>pose suivie&lt;/strong> du casque : sa position et sa rotation évoluent quand l’utilisateur bouge. Le &lt;strong>XR Origin&lt;/strong> transforme ces poses dans le monde Unity. La téléportation déplacera cette origine.&lt;/p>
&lt;p>Ne pilotez pas la caméra XR avec le script FPS du TP précédent. En mode &lt;strong>Floor&lt;/strong>, la hauteur vient du suivi réel : ne rajoutez pas arbitrairement 1,70 m au rig. Le mode &lt;strong>Device&lt;/strong> utilise au contraire un décalage de hauteur, notamment via &lt;strong>Camera Y Offset&lt;/strong>.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h3 id="premier-test-sans-casque">Premier test sans casque&lt;/h3>
&lt;ol>
&lt;li>Dans &lt;code>Assets/Samples/XR Interaction Toolkit/3.3.2/XR Interaction Simulator&lt;/code>, trouvez le prefab &lt;strong>XR Interaction Simulator&lt;/strong>. Glissez-le dans la scène, à côté du rig.&lt;/li>
&lt;li>Lancez Play et cliquez dans &lt;strong>Game&lt;/strong>. Utilisez l’aide affichée par le simulateur pour déplacer le point de vue, puis simuler chaque contrôleur. &lt;strong>Tab&lt;/strong> alterne les modes FPS et manipulation des périphériques ; les commandes affichées dans Game permettent de choisir les contrôleurs. Sur un clavier AZERTY, consultez ou adaptez les bindings du sample si une touche est peu accessible.&lt;/li>
&lt;li>Vérifiez que le point de vue et les deux contrôleurs répondent. Le simulateur produit des entrées XR ; il ne remplace pas les composants du rig.&lt;/li>
&lt;/ol>
&lt;p>N’activez pas en même temps l’insertion automatique du simulateur dans les réglages XRI et le prefab ajouté manuellement. &lt;strong>Désactivez le GameObject du simulateur pour chaque test avec un casque et avant le build.&lt;/strong> Le parcours sans casque vérifie les interactions ; l’échelle perçue, le suivi et le confort devront être évalués dans le casque.&lt;/p>
&lt;p>Référence : &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">installation et commandes du XR Interaction Simulator&lt;/a>.&lt;/p>
&lt;h2 id="3-exécuter-une-première-fois-sur-le-casque">3. Exécuter une première fois sur le casque&lt;/h2>
&lt;p>Faites ce premier essai dès que vous disposez du matériel. Si les casques sont partagés, poursuivez les exercices avec le simulateur en attendant.&lt;/p>
&lt;h3 id="meta-quest-autonome--construire-et-installer-un-apk">Meta Quest autonome : construire et installer un APK&lt;/h3>
&lt;ol>
&lt;li>Le casque doit être préparé pour le développement par l’enseignant ou son propriétaire : &lt;strong>mode développeur activé&lt;/strong>, câble USB de données et autorisation de débogage USB acceptée dans le casque. Dans Unity Hub, vérifiez que l’installation de Unity possède &lt;strong>Android Build Support&lt;/strong>, &lt;strong>Android SDK &amp;amp; NDK Tools&lt;/strong> et &lt;strong>OpenJDK&lt;/strong>.&lt;/li>
&lt;li>Ouvrez &lt;strong>File &amp;gt; Build Profiles&lt;/strong>, sélectionnez &lt;strong>Meta Quest&lt;/strong>, puis &lt;strong>Switch Platform&lt;/strong>. Si vous utilisez le profil &lt;strong>Android&lt;/strong> du poste, basculez sur celui-ci et configurez les réglages Quest ci-dessous. Unity 6.3 fournit un &lt;a href="https://docs.unity3d.com/6000.3/Documentation/Manual/xr-meta-quest-build-profile.html" target="_blank" rel="noopener">profil Meta Quest dédié&lt;/a>, fondé sur Android, qui installe OpenXR et préconfigure plusieurs réglages.&lt;/li>
&lt;li>Dans &lt;strong>Project Settings &amp;gt; XR Plug-in Management&lt;/strong>, onglet &lt;strong>Android&lt;/strong>, activez &lt;strong>OpenXR&lt;/strong> comme fournisseur. Utilisez cette pile pour le TP ; laissez le fournisseur &lt;strong>Oculus&lt;/strong> désactivé.&lt;/li>
&lt;li>Sous &lt;strong>XR Plug-in Management &amp;gt; OpenXR&lt;/strong>, toujours pour Android, activez &lt;strong>Meta Quest Support&lt;/strong> et ajoutez &lt;strong>Oculus Touch Controller Profile&lt;/strong> dans &lt;strong>Interaction Profiles&lt;/strong>. Le nom « Oculus Touch » désigne ici un profil de contrôleur OpenXR. Consultez les &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.openxr@1.16/manual/features/metaquest.html" target="_blank" rel="noopener">réglages Meta Quest Support&lt;/a> pour les profils propres au matériel utilisé.&lt;/li>
&lt;li>Dans &lt;strong>Player &amp;gt; Other Settings&lt;/strong>, vérifiez &lt;strong>Scripting Backend = IL2CPP&lt;/strong> et &lt;strong>Target Architectures = ARM64&lt;/strong>. Avec le profil Meta Quest, conservez les réglages graphiques préconfigurés. Avec Android, appliquez les corrections OpenXR adaptées au casque. Revenez dans &lt;strong>Project Validation&lt;/strong>, plateforme Android, et corrigez les erreurs bloquantes.&lt;/li>
&lt;li>Dans &lt;strong>Build Profiles &amp;gt; Scene List&lt;/strong>, ajoutez la scène ouverte : &lt;code>VRBasics&lt;/code> doit être cochée et première. Donnez à l’application un identifiant unique, par exemple &lt;code>com.votreprenom.vrbasics&lt;/code>, dans les réglages Player.&lt;/li>
&lt;li>Sélectionnez le casque dans &lt;strong>Run Device&lt;/strong>, actualisez la liste si nécessaire, puis faites &lt;strong>Build And Run&lt;/strong>. Enregistrez l’APK dans un dossier &lt;code>Builds&lt;/code> à la racine du projet, hors &lt;code>Assets&lt;/code>. Le premier build est souvent plus long que les suivants.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> dans le casque, regardez la table, tournez la tête et déplacez-vous légèrement dans votre zone physique. Le point de vue doit suivre vos mouvements ; les contrôleurs doivent suivre vos mains. À ce stade, les cubes ne se saisissent pas encore.&lt;/p>
&lt;h3 id="casque-pc--tester-directement-dans-léditeur">Casque PC : tester directement dans l’éditeur&lt;/h3>
&lt;p>Sur un &lt;strong>poste compatible avec le casque et son logiciel PC&lt;/strong>, sélectionnez la plateforme PC et activez &lt;strong>OpenXR dans l’onglet PC&lt;/strong> de XR Plug-in Management. Configurez un profil de contrôleur adapté, démarrez le runtime OpenXR du constructeur, puis lancez Play, simulateur désactivé. Un Quest utilisé en mode Link nécessite aussi sa connexion au logiciel PC ; brancher simplement un câble USB ne crée pas une session PC VR. Les réglages Android ne configurent pas le test PC.&lt;/p>
&lt;aside class="unity-concept unity-concept--advice" role="note" aria-labelledby="unity-concept-4">
&lt;p class="unity-concept__label">Au casque&lt;/p>
&lt;h4 id="unity-concept-4" class="unity-concept__title">Commencer par une expérience confortable&lt;/h4>
&lt;div class="unity-concept__body">Dégagez l’espace de jeu et utilisez les dragonnes. Testez d’abord le suivi naturel et la téléportation ; gardez l’horizon stable. Retirez le casque en cas d’inconfort. Travaillez à deux si possible : une personne teste, l’autre observe la scène et recueille les remarques.&lt;/div>
&lt;/aside>
&lt;h2 id="4-se-déplacer-par-téléportation">4. Se déplacer par téléportation&lt;/h2>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-5">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-5" class="unity-concept__title">Interactor, Interactable et Manager&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>Un &lt;strong>Interactor&lt;/strong> représente un moyen d’agir : rayon d’un contrôleur, contact de proximité… Un &lt;strong>Interactable&lt;/strong> rend une cible disponible : surface de téléportation, objet saisissable ou sélectionnable. Le &lt;strong>XR Interaction Manager&lt;/strong> organise leurs relations.&lt;/p>
&lt;p>Il faut à la fois une cible détectable par ses &lt;strong>colliders&lt;/strong>, des &lt;strong>masques compatibles&lt;/strong> et une &lt;strong>action de sélection&lt;/strong> fonctionnelle. Un rayon visible ne garantit pas que ces conditions soient réunies.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;ol>
&lt;li>Dans &lt;strong>Locomotion&lt;/strong> du rig, repérez &lt;strong>Locomotion Mediator&lt;/strong>, &lt;strong>XR Body Transformer&lt;/strong> et &lt;strong>Teleportation Provider&lt;/strong>. Vérifiez leurs références au rig et au mediator. Conservez cette organisation XRI 3 : les anciens tutoriels utilisant &lt;strong>Locomotion System&lt;/strong> ne décrivent pas cette configuration.&lt;/li>
&lt;li>Sur &lt;code>Ground&lt;/code>, ajoutez &lt;strong>Teleportation Area&lt;/strong>. Renseignez le &lt;strong>XR Interaction Manager&lt;/strong> et le &lt;strong>Teleportation Provider&lt;/strong> du rig dans les champs correspondants. Vérifiez que le collider du sol figure dans la liste &lt;strong>Colliders&lt;/strong>, ou laissez la recherche automatique si elle le prend bien en compte.&lt;/li>
&lt;li>Réglez son &lt;strong>Interaction Layer Mask&lt;/strong> sur &lt;strong>Teleport&lt;/strong>. Si cette couche manque, créez-la dans &lt;strong>Project Settings &amp;gt; XR Plug-in Management &amp;gt; XR Interaction Toolkit &amp;gt; Interaction Layers&lt;/strong>, puis utilisez-la sur la surface et le rayon de téléportation.&lt;/li>
&lt;li>Sur les contrôleurs du rig, trouvez les interactors dédiés à la téléportation. Leur &lt;strong>Interaction Layer Mask&lt;/strong> doit inclure &lt;strong>Teleport&lt;/strong>, et leur &lt;strong>Raycast Mask&lt;/strong> doit inclure le layer physique du sol. Gardez les actions &lt;strong>Teleport Mode&lt;/strong> et de validation préconfigurées par le sample. Sur le &lt;strong>Controller Input Action Manager&lt;/strong> de chaque contrôleur, décochez &lt;strong>Smooth Motion Enabled&lt;/strong> et &lt;strong>Smooth Turn Enabled&lt;/strong> pour commencer avec la téléportation et la rotation par crans. Le prefab active initialement le déplacement continu à gauche et la téléportation à droite.&lt;/li>
&lt;li>Dans &lt;strong>XRI Default Input Actions&lt;/strong>, retrouvez les actions des maps &lt;strong>XRI Left Locomotion&lt;/strong> et &lt;strong>XRI Right Locomotion&lt;/strong>. Lisez les bindings de &lt;strong>Teleport Mode&lt;/strong> et &lt;strong>Teleport Mode Cancel&lt;/strong>. Avec les bindings standards des contrôleurs à joystick, poussez le joystick vers l’avant pour viser, puis relâchez pour valider ; vérifiez le binding effectif et &lt;strong>Teleport Trigger&lt;/strong> sur la surface si le comportement diffère.&lt;/li>
&lt;li>Testez trois destinations sur le sol. Essayez aussi de viser la table : elle doit rester une surface non téléportable. Observez le Transform racine du XR Origin pendant le test.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Deux masques à distinguer :&lt;/strong> le &lt;strong>Layer&lt;/strong> physique d’un GameObject et le &lt;strong>Raycast Mask&lt;/strong> filtrent les collisions/raycasts Unity ; l’&lt;strong>Interaction Layer Mask&lt;/strong> filtre les relations XRI. Créer une couche physique nommée Teleport ne crée pas la couche d’interaction XRI. Voir les &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/interaction-layers.html" target="_blank" rel="noopener">couches d’interaction&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Vérification :&lt;/strong> une destination valide affiche le retour visuel du rayon ; la validation déplace le rig sur le sol sans perdre la hauteur suivie du casque. Si la couleur ne change pas, cherchez d’abord les colliders et masques, puis les actions.&lt;/p>
&lt;h3 id="comparer-les-modes-de-navigation">Comparer les modes de navigation&lt;/h3>
&lt;p>Repérez &lt;strong>Snap Turn Provider&lt;/strong>, &lt;strong>Continuous Turn Provider&lt;/strong> et le fournisseur de déplacement continu (&lt;strong>Dynamic Move Provider&lt;/strong> dans les Starter Assets). Commencez avec &lt;strong>téléportation et rotation par crans&lt;/strong> ; utilisez les paramètres des &lt;strong>Controller Input Action Manager&lt;/strong> des contrôleurs pour choisir la rotation par crans et désactiver le déplacement continu. Vérifiez qu’un seul mode de rotation est actif.&lt;/p>
&lt;p>Une fois le parcours fonctionnel, essayez brièvement le déplacement continu si vous êtes à l’aise, puis revenez à la configuration initiale. &lt;strong>À expliquer :&lt;/strong> qu’est-ce qui déplace la caméra quand on marche réellement ? Et quand on pousse le joystick ? Quel mode rend les distances et l’orientation les plus faciles à comprendre ?&lt;/p>
&lt;p>Référence : &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-saisir-et-lancer-les-cubes">5. Saisir et lancer les cubes&lt;/h2>
&lt;ol>
&lt;li>Sur un cube, ajoutez &lt;strong>XR Grab Interactable&lt;/strong>. Gardez son &lt;strong>Box Collider&lt;/strong>, non trigger, et son &lt;strong>Rigidbody&lt;/strong>. Renseignez le manager. Gardez &lt;strong>Interaction Layer Mask = Default&lt;/strong> pour les objets manipulables et vérifiez que les interactors de manipulation du rig acceptent cette couche.&lt;/li>
&lt;li>Choisissez &lt;strong>Select Mode = Single&lt;/strong> et &lt;strong>Movement Type = Velocity Tracking&lt;/strong>. Laissez &lt;strong>Throw On Detach&lt;/strong> activé. Ce mode utilise le Rigidbody pendant le suivi et permet de commencer à observer les collisions ; il faudra tout de même éviter les gestes très rapides à travers la table.&lt;/li>
&lt;li>Testez la saisie à proximité avec le &lt;strong>grip&lt;/strong> du contrôleur, puis le relâchement. Essayez ensuite à distance avec le rayon de manipulation du rig. Inspectez le binding &lt;strong>Select&lt;/strong> de la map &lt;strong>XRI Right Interaction&lt;/strong> ou &lt;strong>XRI Left Interaction&lt;/strong> pour confirmer le bouton utilisé.&lt;/li>
&lt;li>Faites un lancer doux, à l’écart de vous. Le cube doit retomber et rester sur le sol. Le changement de vitesse vient du relâchement suivi par XRI : aucun script de projectile n’est nécessaire ici.&lt;/li>
&lt;li>Faites du cube un prefab &lt;code>GrabbableCube&lt;/code> dans &lt;code>Assets/Prefabs&lt;/code>. Équipez les autres cubes de la même façon ou remplacez-les par des instances. Gardez les objets de téléportation sur leur couche dédiée.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Vérification :&lt;/strong> vous pouvez prendre un cube, le poser sur la table, le reprendre et le lancer. Un seul contrôleur le tient à la fois. Comparez, sur une copie, &lt;strong>Velocity Tracking&lt;/strong>, &lt;strong>Kinematic&lt;/strong> et &lt;strong>Instantaneous&lt;/strong> : observez la réactivité et les contacts avec la table, puis restaurez le mode initial.&lt;/p>
&lt;p>&lt;strong>Question :&lt;/strong> quelle différence y a-t-il entre déplacer un Transform et demander à un Rigidbody de suivre une cible ? Pourquoi un objet peut-il sembler plus réactif tout en traversant plus facilement un obstacle ? Voir les &lt;a href="https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@3.3/manual/xr-grab-interactable.html" target="_blank" rel="noopener">modes de XR Grab Interactable&lt;/a>.&lt;/p>
&lt;h2 id="6-créer-un-cube-avec-une-action-du-contrôleur">6. Créer un cube avec une action du contrôleur&lt;/h2>
&lt;p>Vous avez utilisé une action clavier ou souris dans le TP Unity. Le principe reste le même : une &lt;strong>action&lt;/strong> représente l’intention, un &lt;strong>binding&lt;/strong> la relie à un contrôle matériel. Ici, vous allez créer une action dédiée afin qu’un appui pour saisir un objet ne crée pas aussi un cube.&lt;/p>
&lt;h3 id="définir-laction">Définir l’action&lt;/h3>
&lt;ol>
&lt;li>Dans &lt;code>Assets&lt;/code>, créez un dossier &lt;code>Input&lt;/code>, puis un asset &lt;strong>Create &amp;gt; Input Actions&lt;/strong>, nommé &lt;code>VRLabActions&lt;/code>.&lt;/li>
&lt;li>Double-cliquez dessus et créez une &lt;strong>Action Map&lt;/strong> &lt;code>Lab&lt;/code>, puis une action &lt;strong>Spawn&lt;/strong>, &lt;strong>Action Type = Button&lt;/strong>.&lt;/li>
&lt;li>Ajoutez le binding &lt;strong>&lt;code>&amp;lt;XRController&amp;gt;{RightHand}/secondaryButton&lt;/code>&lt;/strong> : bouton secondaire du contrôleur droit, généralement &lt;strong>B&lt;/strong> sur Quest. Gardez le déclenchement à l’appui ; n’utilisez pas une action de pose de type Value ou Pass Through.&lt;/li>
&lt;li>Ajoutez aussi un binding &lt;strong>&lt;code>&amp;lt;Keyboard&amp;gt;/n&lt;/code>&lt;/strong> pour vérifier votre script sans casque. Enregistrez l’asset avec &lt;strong>Save Asset&lt;/strong> si l’enregistrement automatique n’est pas actif.&lt;/li>
&lt;/ol>
&lt;h3 id="écrire-et-brancher-objectspawn">Écrire et brancher ObjectSpawn&lt;/h3>
&lt;p>Créez &lt;code>ObjectSpawn.cs&lt;/code> avec &lt;strong>Create &amp;gt; Scripting &amp;gt; MonoBehaviour Script&lt;/strong>, puis ajoutez-le à un GameObject vide &lt;code>CubeSpawner&lt;/code>. Le fichier et la classe doivent porter le même nom.&lt;/p>
&lt;p>Exposez dans l’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">// Champs à placer dans la classe ObjectSpawn : MonoBehaviour.&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>À réaliser :&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>Créez un GameObject vide &lt;code>SpawnPoint&lt;/code> &lt;strong>à la racine de la scène&lt;/strong>, position &lt;code>(0, 1.2, 2)&lt;/code>, rotation &lt;code>(0, 0, 0)&lt;/code>, échelle &lt;code>(1, 1, 1)&lt;/code>. Vérifiez dans Scene que ce point se trouve au-dessus du plateau. Il n’est pas enfant de la table et n’hérite donc pas de son échelle.&lt;/li>
&lt;li>Assignez &lt;strong>VRLabActions &amp;gt; Lab/Spawn&lt;/strong>, le prefab &lt;strong>GrabbableCube&lt;/strong> depuis Project et le Transform &lt;strong>SpawnPoint&lt;/strong> aux trois champs du script.&lt;/li>
&lt;li>Dans &lt;strong>OnEnable&lt;/strong>, abonnez une méthode nommée, par exemple &lt;code>SpawnCube(InputAction.CallbackContext context)&lt;/code>, à &lt;strong>&lt;code>spawnAction.action.performed&lt;/code>&lt;/strong>, puis activez l’action avec &lt;strong>&lt;code>Enable()&lt;/code>&lt;/strong>.&lt;/li>
&lt;li>Dans &lt;strong>OnDisable&lt;/strong>, désabonnez la même méthode avec &lt;strong>&lt;code>-=&lt;/code>&lt;/strong>, puis appelez &lt;strong>&lt;code>Disable()&lt;/code>&lt;/strong>. Ce composant est le seul propriétaire de l’activation de cette action dédiée. Ne faites pas ce &lt;code>Disable()&lt;/code> sur une action partagée du rig. N’ajoutez donc pas &lt;code>VRLabActions&lt;/code> à un autre Input Action Manager pour cet exercice.&lt;/li>
&lt;li>Dans la méthode appelée, utilisez &lt;strong>&lt;code>Instantiate(cubePrefab, spawnPoint.position, spawnPoint.rotation)&lt;/code>&lt;/strong>. Vérifiez les références avant de les utiliser et limitez les essais à quelques cubes pour éviter d’accumuler des objets physiques.&lt;/li>
&lt;/ol>
&lt;p>Les extraits sont des repères : &lt;strong>complétez vous-même la classe et les méthodes&lt;/strong>. Comme dans le TP Unity, attendez la compilation et corrigez la première erreur de Console avant de lancer Play.&lt;/p>
&lt;p>&lt;strong>Vérification :&lt;/strong> un appui sur N fait apparaître un seul cube au-dessus de la table ; le maintenir ne doit pas produire un cube à chaque frame. Désactivez puis réactivez &lt;code>CubeSpawner&lt;/code> dans l’Inspector pendant Play : chaque appui doit encore produire un seul cube. Testez ensuite le bouton secondaire droit dans le casque. Le cube créé doit être saisissable.&lt;/p>
&lt;p>&lt;strong>À expliquer :&lt;/strong> pourquoi utiliser une méthode nommée pour pouvoir se désabonner ? Pourquoi créer une action &lt;code>Spawn&lt;/code> plutôt que réutiliser &lt;code>Select&lt;/code> ? Référence : &lt;a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.20/manual/Actions.html" target="_blank" rel="noopener">actions de l’Input System&lt;/a>.&lt;/p>
&lt;h2 id="7-réagir-au-survol-et-à-la-sélection-dun-objet">7. Réagir au survol et à la sélection d’un objet&lt;/h2>
&lt;p>Ajoutez sur la table une petite borne colorée, à côté des cubes, qui reste fixe. L’objectif est de &lt;strong>signaler le survol&lt;/strong>, puis de &lt;strong>changer durablement sa couleur à chaque sélection&lt;/strong>.&lt;/p>
&lt;ol>
&lt;li>Créez un cube &lt;code>ColorTarget&lt;/code>, position &lt;code>(0.55, 0.95, 2)&lt;/code>, échelle &lt;code>(0.2, 0.3, 0.2)&lt;/code>. Conservez son Box Collider. Ajoutez &lt;strong>XR Simple Interactable&lt;/strong>, manager assigné, &lt;strong>Interaction Layer Mask = Default&lt;/strong>, &lt;strong>Select Mode = Single&lt;/strong>. Ne lui ajoutez pas XR Grab Interactable : cette borne reste sur la table.&lt;/li>
&lt;li>Créez &lt;code>TargetFeedback.cs&lt;/code> et attachez-le à la borne. Exposez une référence au &lt;strong>Renderer&lt;/strong>, deux couleurs persistantes et une couleur de survol. Assignez le Renderer dans l’Inspector.&lt;/li>
&lt;li>Écrivez trois méthodes publiques sans paramètre : &lt;strong>&lt;code>BeginHover()&lt;/code>&lt;/strong>, &lt;strong>&lt;code>EndHover()&lt;/code>&lt;/strong> et &lt;strong>&lt;code>ToggleColor()&lt;/code>&lt;/strong>. Les deux premières changent l’état de survol ; la troisième alterne la couleur persistante. Une méthode commune met à jour l’affichage : couleur de survol si la borne est visée, sinon couleur persistante. Ainsi, quitter le survol révèle bien la dernière couleur choisie.&lt;/li>
&lt;li>Pour isoler la couleur de cette borne, vous pouvez obtenir &lt;strong>une seule fois&lt;/strong> une instance du matériau via &lt;code>targetRenderer.material&lt;/code> dans &lt;strong>Awake&lt;/strong>, la conserver, puis modifier sa propriété &lt;strong>color&lt;/strong>. Détruisez cette instance dans &lt;strong>OnDestroy&lt;/strong>. Modifier &lt;strong>sharedMaterial&lt;/strong> changerait aussi les autres objets utilisant cet asset ; n’en créez pas une nouvelle à chaque frame.&lt;/li>
&lt;li>Dépliez &lt;strong>Interactable Events&lt;/strong> du XR Simple Interactable. Dans chaque événement, cliquez sur &lt;strong>+&lt;/strong>, glissez le GameObject &lt;code>ColorTarget&lt;/code> dans le champ objet, puis choisissez la méthode du composant &lt;strong>TargetFeedback&lt;/strong> :&lt;/li>
&lt;/ol>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Événement XRI&lt;/th>
&lt;th>Méthode à appeler&lt;/th>
&lt;th>Effet attendu&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>Activer le retour visuel au premier rayon/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>Retirer le retour visuel après le dernier rayon/contact.&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>Alterner la couleur persistante une fois par sélection.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Les événements &lt;strong>First/Last&lt;/strong> évitent qu’un contrôleur quittant la borne efface le survol alors que l’autre la vise encore. Dans le menu des fonctions UnityEvent, sélectionnez les versions &lt;strong>sans paramètre&lt;/strong> de vos méthodes.&lt;/p>
&lt;p>&lt;strong>Vérification :&lt;/strong> visez la borne avec un contrôleur, sélectionnez-la avec le &lt;strong>grip&lt;/strong> correspondant à &lt;strong>Select&lt;/strong>, puis détournez le rayon : la nouvelle couleur doit rester. Visez-la avec les deux contrôleurs et retirez-en un seul : elle doit rester en survol.&lt;/p>
&lt;aside class="unity-concept" role="note" aria-labelledby="unity-concept-6">
&lt;p class="unity-concept__label">Notion essentielle&lt;/p>
&lt;h4 id="unity-concept-6" class="unity-concept__title">Select et Activate ne désignent pas la même action&lt;/h4>
&lt;div class="unity-concept__body">&lt;p>&lt;strong>Hover&lt;/strong> signifie qu’un interactor considère la cible comme survolée. &lt;strong>Select&lt;/strong> établit une sélection, par exemple une saisie. &lt;strong>Activate&lt;/strong> représente une action supplémentaire sur un objet sélectionné, souvent liée à la gâchette avec les bindings du rig.&lt;/p>
&lt;p>Dans cet exercice, « cliquer » sur la borne signifie &lt;strong>Select&lt;/strong> : ne supposez pas que la gâchette provoque cet événement. Retrouvez le binding effectif dans les actions du rig. Les événements du &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> se branchent dans l’Inspector ; votre script décrit le comportement à déclencher.&lt;/p>
&lt;/div>
&lt;/aside>
&lt;h2 id="8-vérifier-lapplication-et-rendre-compte">8. Vérifier l’application et rendre compte&lt;/h2>
&lt;p>Arrêtez Play, sauvegardez et désactivez le simulateur. Reconstruisez l’application et refaites le parcours sur le casque cible.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Test&lt;/th>
&lt;th>Résultat attendu&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Tourner et déplacer la tête&lt;/td>
&lt;td>Vue suivie, hauteur et échelle cohérentes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Déplacer les deux contrôleurs&lt;/td>
&lt;td>Poses et rayons visibles, sans décalage inexpliqué.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Téléporter sur le sol, puis viser la table&lt;/td>
&lt;td>Sol accepté, table refusée, hauteur préservée.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Saisir, poser et lancer un cube&lt;/td>
&lt;td>Objet manipulable et contacts avec le sol et la table.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Appuyer sur le bouton Spawn&lt;/td>
&lt;td>Un cube par appui, créé au point prévu et saisissable.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Survoler et sélectionner la borne&lt;/td>
&lt;td>Survol temporaire, couleur choisie conservée en sortie.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Suivez les &lt;a href="../../tutorial/rapport-tp/">consignes du compte rendu&lt;/a> de votre séance. Présentez le matériel et les versions, une capture de la hiérarchie du rig, les scripts réalisés et les résultats des tests. Distinguez les essais &lt;strong>simulateur&lt;/strong>, &lt;strong>Play avec casque PC&lt;/strong> et &lt;strong>APK autonome&lt;/strong> ; signalez les tests matériels que vous n’avez pas pu effectuer.&lt;/p>
&lt;p>Répondez notamment aux questions suivantes :&lt;/p>
&lt;ul>
&lt;li>Quel est le rôle du XR Origin par rapport à la caméra suivie ?&lt;/li>
&lt;li>Quelle différence faites-vous entre layer physique et couche d’interaction XRI ?&lt;/li>
&lt;li>Quel chemin relie un bouton physique à votre méthode de création de cube ?&lt;/li>
&lt;li>Pourquoi distinguer le survol, la sélection et l’activation ?&lt;/li>
&lt;li>Quel mode de déplacement choisiriez-vous pour une première découverte de votre scène, et pourquoi ?&lt;/li>
&lt;/ul>
&lt;h3 id="pour-aller-plus-loin">Pour aller plus loin&lt;/h3>
&lt;ul>
&lt;li>Ajouter un &lt;strong>Teleportation Anchor&lt;/strong> pour rejoindre un point d’observation et comparer avec une zone libre.&lt;/li>
&lt;li>Ajouter un &lt;strong>XR Socket Interactor&lt;/strong> pour ranger un cube à une position précise.&lt;/li>
&lt;li>Ajouter une interface &lt;strong>World Space&lt;/strong> avec un &lt;strong>XR UI Input Module&lt;/strong> et un &lt;strong>Tracked Device Graphic Raycaster&lt;/strong> ; utiliser le rayon pour actionner un bouton de remise à zéro.&lt;/li>
&lt;li>Explorer le retour haptique et vérifier son effet sur le casque, puisque le simulateur ne restitue pas la sensation.&lt;/li>
&lt;li>Poursuivre avec le &lt;a href="../cybersickness-confort/">TP sur le confort en VR&lt;/a> ou le &lt;a href="../vr-engineering/">TP XR Engineering &amp;amp; Debugging&lt;/a> pour structurer et instrumenter une application plus importante.&lt;/li>
&lt;/ul>
&lt;h2 id="en-cas-de-blocage">En cas de blocage&lt;/h2>
&lt;p>Corrigez d’abord la première erreur rouge de &lt;strong>Console&lt;/strong>. Indiquez toujours l’étape, la plateforme active, les versions et le message exact lorsque vous demandez de l’aide.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Symptôme&lt;/th>
&lt;th>Vérifications prioritaires&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Caméra immobile ou mauvaise hauteur&lt;/td>
&lt;td>Un seul rig et une seule caméra active ; référence Camera du XR Origin ; mode Floor/Device ; simulateur actif uniquement sans casque ; fournisseur OpenXR de la plateforme cible.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Casque détecté en USB mais aucune VR dans Play&lt;/td>
&lt;td>Une connexion USB pour installer un APK ne suffit pas pour le PC VR. Vérifiez runtime PC, mode Link éventuel et fournisseur OpenXR PC, ou utilisez Build And Run pour l’autonome.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Les mains ou rayons ne répondent pas&lt;/td>
&lt;td>Input System actif, Input Action Manager et actions du sample ; profils OpenXR adaptés ; contrôleurs réveillés ; aucune ancienne caméra FPS active.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Le simulateur ne répond pas&lt;/td>
&lt;td>Game a le focus ; un seul simulateur ; rig préconfiguré présent ; aide et bindings du sample ; pas de session casque concurrente.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rayon visible, aucune téléportation&lt;/td>
&lt;td>Collider du sol, couche XRI Teleport des deux côtés, Raycast Mask, provider et mediator, action Teleport Mode, mode de déclenchement de la surface.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Un cube ne se saisit pas ou traverse le décor&lt;/td>
&lt;td>Collider, Rigidbody, XR Grab Interactable, manager, masques de manipulation et action Select ; vérifier Movement Type et la vitesse du geste.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>N fonctionne mais pas le bouton du casque&lt;/td>
&lt;td>Binding RightHand/secondaryButton, profil OpenXR, test sur la bonne main et APK reconstruit après modification.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Aucun cube ou plusieurs cubes par appui&lt;/td>
&lt;td>Références de ObjectSpawn, action activée, abonnement unique et désabonnement ; pas d’instanciation dans Update ; pas de doublon CubeSpawner.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>La couleur change sur tous les objets&lt;/td>
&lt;td>Le script modifie probablement sharedMaterial ; utiliser une instance conservée pour la borne.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>APK impossible à construire ou casque absent&lt;/td>
&lt;td>Modules Android du bon éditeur, erreurs de Project Validation, autorisation USB, câble de données, Run Device, IL2CPP/ARM64. Sur un poste Linux, vérifier aussi les droits d’accès USB et les outils de compilation demandés par Unity.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Ancienne scène dans le casque&lt;/td>
&lt;td>Scene List et profil actifs, sauvegarde de VRBasics, nouveau Build And Run terminé sans erreur.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="pour-poursuivre">Pour poursuivre&lt;/h3>
&lt;p>Le TP &lt;a href="../3d-interaction/">Interaction 3D : comparer les techniques de manipulation&lt;/a> propose une tâche de construction chronométrée pour comparer saisie directe, rayon virtuel, Near Far et utilisation des deux mains.&lt;/p>
&lt;h3 id="documentation-de-référence">Documentation de référence&lt;/h3>
&lt;p>Documentation consultée le &lt;strong>15 octobre 2026&lt;/strong>. Utilisez la branche &lt;strong>3.3&lt;/strong> de XRI pour suivre les noms de ce TP.&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 et composants&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">Profil de build Meta Quest dans 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">Configuration OpenXR pour 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">Actions de l’Input System&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>TP — Ingénierie et débogage en réalité virtuelle</title><link>https://www.etiennepeillard.com/fr/teaching/lab/vr-engineering/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.etiennepeillard.com/fr/teaching/lab/vr-engineering/</guid><description>&lt;!--
- Donner des assets de base
- Donner une scène et les composants pour que le focus soit sur la logique de développement et pas les éléments et interactions de base
- Pas sûr qu'ils aient pigé la logique événementielle, il faudrait peut-être ajouter des exemples guidés (par exemple le scale)
- Ils ont pas compris MVC : ils skippent le modèle dans les autres étapes quand ca interagie directement avec la vue (genre scale) → ajouter une première implémentation guidée de la scale et expliquer MVC
- -->
&lt;h2 id="objectif">Objectif&lt;/h2>
&lt;p>Ce TP (~7h) a pour objectif de vous apprendre à &lt;strong>développer et maintenir une application VR&lt;/strong> avec :&lt;/p>
&lt;ul>
&lt;li>une &lt;strong>architecture claire&lt;/strong> (séparation des responsabilités, dépendances explicites),&lt;/li>
&lt;li>une &lt;strong>chaîne d’itération efficace&lt;/strong> (tester un maximum sans casque, builds debug),&lt;/li>
&lt;li>une &lt;strong>observabilité minimale&lt;/strong> (overlay VR, logs structurés),&lt;/li>
&lt;li>des &lt;strong>pratiques de debugging XR&lt;/strong>,&lt;/li>
&lt;li>une &lt;strong>introduction au profiling VR&lt;/strong> (optionnel selon le temps restant).&lt;/li>
&lt;/ul>
&lt;p>L’application développée est un &lt;strong>atelier VR de visualisation du système solaire&lt;/strong> : un modèle “tabletop” manipulable, une UI en world-space, des modes de vue, et des contrôles temporels.&lt;/p>
&lt;hr>
&lt;h2 id="pré-requis">Pré-requis&lt;/h2>
&lt;h3 id="compétences">Compétences&lt;/h3>
&lt;ul>
&lt;li>Bases C# / Unity (scènes, prefabs, scripts)&lt;/li>
&lt;li>Notions Git (commit, tag, push)&lt;/li>
&lt;li>Bases VR (confort, world-space UI) utiles mais pas indispensables&lt;/li>
&lt;/ul>
&lt;h3 id="outils">Outils&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Unity LTS&lt;/strong> (2022.3 ou 2023 LTS)&lt;/li>
&lt;li>Packages Unity :
&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>Un casque VR compatible OpenXR&lt;/li>
&lt;/ul>
&lt;h3 id="ressources">Ressources&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="PlanetData.cs">PlanetData.cs&lt;/a> (fournit les données et fonctions de calcul orbital)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="règles-dingénierie-à-respecter">Règles d’ingénierie (à respecter)&lt;/h2>
&lt;p>Dans ce TP, vous devez explicitement appliquer les règles suivantes :&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Aucune dépendance implicite&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Pas de &lt;code>FindObjectOfType&lt;/code>, pas de &lt;code>GameObject.Find&lt;/code>, pas de dépendance “magique”.&lt;/li>
&lt;li>Les références nécessaires sont injectées via l’inspecteur ou via un &lt;em>bootstrapper&lt;/em>.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Pas de logique métier dans &lt;code>Update()&lt;/code> par défaut&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;code>Update()&lt;/code> est réservé aux besoins continus &lt;em>réellement nécessaires&lt;/em>.&lt;/li>
&lt;li>Préférer événements, actions, timers contrôlés.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Séparation des responsabilités&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Modèle de données / services (calculs, temps, conversion) ≠ vues (GameObjects) ≠ contrôleurs.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Debuggabilité&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Tout comportement critique doit produire des logs exploitables.&lt;/li>
&lt;li>Mise en place d’un &lt;strong>DebugOverlay&lt;/strong> visible dans le casque.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Performance&lt;/strong> &lt;em>(optionnel)&lt;/em>&lt;/p>
&lt;ul>
&lt;li>Interdiction d’allocations évitables par frame (strings/LINQ).&lt;/li>
&lt;li>Profiling obligatoire (preuve avant/après sur un point).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="scénario--solar-system-vr-workbench">Scénario : Solar System VR Workbench&lt;/h2>
&lt;p>Vous développerez une application VR contenant :&lt;/p>
&lt;ul>
&lt;li>un “tabletop” du système solaire (échelle manipulable),&lt;/li>
&lt;li>des planètes positionnées à un instant &lt;em>t&lt;/em> (les fonctions de calcul sont déjà fournies par &lt;code>PlanetData&lt;/code>),&lt;/li>
&lt;li>un contrôle du temps (date + vitesse),&lt;/li>
&lt;li>des trajectoires activables/désactivables,&lt;/li>
&lt;li>un mode “focus planète” (centrage/inspection),&lt;/li>
&lt;li>une UI VR (world-space) pour piloter l’application,&lt;/li>
&lt;li>un overlay debug (FPS, état, derniers événements, warnings).&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="organisation-du-tp">Organisation du TP&lt;/h2>
&lt;p>Le TP est structuré en &lt;strong>5 étapes principales&lt;/strong> et &lt;strong>2 extensions optionnelles&lt;/strong>.&lt;/p>
&lt;p>Obligatoire :&lt;/p>
&lt;p>1 — Boot XR&lt;br>
2 — Architecture&lt;br>
3 — Événements / simulation&lt;br>
4 — Interactions VR&lt;br>
5 — UI + Debug overlay&lt;/p>
&lt;p>Optionnel (si temps restant) :&lt;/p>
&lt;p>6 — Debugging XR guidé&lt;br>
7 — Optimisation de performance&lt;/p>
&lt;hr>
&lt;h2 id="organisation-du-rendu-git">Organisation du rendu (Git)&lt;/h2>
&lt;p>Vous devez livrer :&lt;/p>
&lt;ul>
&lt;li>un &lt;strong>repo Git&lt;/strong> (lien),&lt;/li>
&lt;li>des &lt;strong>tags Git&lt;/strong> à chaque étape (v1.0, v1.1, … v2.0),&lt;/li>
&lt;li>un &lt;strong>court compte-rendu&lt;/strong> (README ou PDF) :
&lt;ul>
&lt;li>captures d’écran (dans le casque ou miroir),&lt;/li>
&lt;li>fonctionnalités implémentées,&lt;/li>
&lt;li>schéma d’architecture (simple, lisible),&lt;/li>
&lt;li>une “debug story” (si vous avez traité l’étape 6),&lt;/li>
&lt;li>une “perf story” (si vous avez traité l’étape 7).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="rappel-tags">Rappel tags&lt;/h3>
&lt;p>À chaque étape :&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="concepts-utiles-pour-ce-tp">Concepts utiles pour ce TP&lt;/h2>
&lt;p>Avant de commencer le TP, voici quelques notions importantes que nous allons utiliser pendant toute la séance. Elles sont très courantes dans les projets Unity et encore plus importantes en XR.&lt;/p>
&lt;h3 id="1--logique-métier">1 — Logique métier&lt;/h3>
&lt;p>La &lt;strong>logique métier&lt;/strong> correspond aux règles qui définissent comment fonctionne votre application, indépendamment de l’affichage.&lt;/p>
&lt;p>Exemples dans ce TP :&lt;/p>
&lt;p>Logique métier :&lt;/p>
&lt;ul>
&lt;li>calculer la position d’une planète à une date donnée&lt;/li>
&lt;li>changer la vitesse du temps&lt;/li>
&lt;li>activer ou désactiver les trajectoires&lt;/li>
&lt;li>changer l’échelle du système solaire&lt;/li>
&lt;/ul>
&lt;p>Affichage (pas logique métier) :&lt;/p>
&lt;ul>
&lt;li>déplacer un GameObject&lt;/li>
&lt;li>changer un texte dans l’UI&lt;/li>
&lt;li>afficher un panneau&lt;/li>
&lt;/ul>
&lt;h6 id="mauvais-exemple">Mauvais exemple&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>Le calcul et l’affichage sont mélangés.&lt;/p>
&lt;h6 id="bon-exemple">Bon exemple&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>La logique décide &lt;strong>quand&lt;/strong> on met à jour, la vue décide &lt;strong>comment&lt;/strong> on affiche.&lt;/p>
&lt;h3 id="2--séparation-des-responsabilités">2 — Séparation des responsabilités&lt;/h3>
&lt;p>Dans un projet Unity, il est très facile de mettre beaucoup de choses dans un seul script. Cela fonctionne pour de petits projets, mais devient rapidement difficile à maintenir.&lt;/p>
&lt;p>On sépare donc les rôles.&lt;/p>
&lt;h6 id="structure-simple-utilisée-dans-ce-tp">Structure simple utilisée dans ce TP&lt;/h6>
&lt;p>Data / Model ←→ Services ←→ Controllers ←→ Views&lt;/p>
&lt;h6 id="model-données">Model (données)&lt;/h6>
&lt;p>Contient uniquement des données.&lt;/p>
&lt;p>&lt;em>Exemple :&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>Un modèle ne dépend normalement pas de Unity.&lt;/p>
&lt;h6 id="services">Services&lt;/h6>
&lt;p>Contiennent des calculs ou des règles.&lt;/p>
&lt;p>&lt;em>Exemple :&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>Coordonnent l’application.&lt;/p>
&lt;p>Ils reçoivent des événements et mettent à jour les vues.&lt;/p>
&lt;p>&lt;em>Exemple :&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>SolarSystemController&lt;/p>
&lt;/blockquote>
&lt;h6 id="views">Views&lt;/h6>
&lt;p>Objets Unity visibles.&lt;/p>
&lt;p>&lt;em>Exemples :&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="résumé">Résumé&lt;/h6>
&lt;ul>
&lt;li>Model → stocker l’état&lt;/li>
&lt;li>Service → calculer&lt;/li>
&lt;li>Controller → décider quoi faire&lt;/li>
&lt;li>View → afficher&lt;/li>
&lt;/ul>
&lt;h3 id="3--bootstrapper">3 — Bootstrapper&lt;/h3>
&lt;h6 id="le-problème">Le problème&lt;/h6>
&lt;p>Dans beaucoup de projets Unity on trouve :&lt;/p>
&lt;ul>
&lt;li>FindObjectOfType&lt;/li>
&lt;li>GameObject.Find&lt;/li>
&lt;li>des Singletons utilisés partout&lt;/li>
&lt;/ul>
&lt;p>Ces méthodes fonctionnent mais rendent le projet fragile.&lt;/p>
&lt;h6 id="solution-simple">Solution simple&lt;/h6>
&lt;p>Créer un objet qui initialise l’application.&lt;/p>
&lt;p>C’est ce qu’on appelle un &lt;strong>Bootstrapper&lt;/strong>.&lt;/p>
&lt;p>Exemple :&lt;/p>
&lt;blockquote>
&lt;p>AppBootstrapper&lt;/p>
&lt;/blockquote>
&lt;p>Son rôle :&lt;/p>
&lt;ul>
&lt;li>créer les services&lt;/li>
&lt;li>connecter les objets&lt;/li>
&lt;li>initialiser les données&lt;/li>
&lt;/ul>
&lt;h6 id="exemple-simple">Exemple simple&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>Les dépendances deviennent visibles et contrôlées.&lt;/p>
&lt;h3 id="4--événements">4 — Événements&lt;/h3>
&lt;p>Les événements permettent à différents objets de communiquer sans être fortement couplés.&lt;/p>
&lt;p>Sans événements :&lt;/p>
&lt;p>UI → Manager → Controller → Camera&lt;/p>
&lt;p>Tout dépend de tout.&lt;/p>
&lt;p>Avec événements :&lt;/p>
&lt;p>UI → Event → Objets intéressés&lt;/p>
&lt;p>Chaque système reste indépendant.&lt;/p>
&lt;h6 id="exemple">Exemple&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>Quand la date 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="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>Un autre script peut écouter cet événement :&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>Une &lt;strong>Action&lt;/strong> est simplement une fonction stockée dans une variable.&lt;/p>
&lt;p>Exemple :&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>Dans Unity on les utilise pour :&lt;/p>
&lt;ul>
&lt;li>événements&lt;/li>
&lt;li>callbacks&lt;/li>
&lt;li>interactions UI&lt;/li>
&lt;/ul>
&lt;h3 id="6--gestion-de-létat-de-la-simulation">6 — Gestion de l’état de la simulation&lt;/h3>
&lt;p>Dans cette application, la variable centrale n’est pas vraiment un &amp;ldquo;timer&amp;rdquo; technique. Ce qui compte est simplement &lt;strong>la date de la simulation&lt;/strong>.&lt;/p>
&lt;p>Autrement dit, on manipule un &lt;strong>état global&lt;/strong> :&lt;/p>
&lt;p>Date simulée → positions des planètes → affichage&lt;/p>
&lt;p>Lorsque cette date change :&lt;/p>
&lt;ul>
&lt;li>les positions des planètes doivent être recalculées&lt;/li>
&lt;li>certaines visualisations doivent être mises à jour&lt;/li>
&lt;li>l’interface peut afficher la nouvelle valeur&lt;/li>
&lt;/ul>
&lt;p>Plutôt que de recalculer en permanence dans &lt;code>Update()&lt;/code>, on déclenche un &lt;strong>événement uniquement quand l’état change&lt;/strong>.&lt;/p>
&lt;h4 id="mauvaise-approche">Mauvaise approche&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>Chaque objet vérifie en permanence si quelque chose a changé.&lt;/p>
&lt;h4 id="approche-recommandée">Approche recommandée&lt;/h4>
&lt;p>On utilise un événement qui signale un changement d’état.&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>Quand la date change :&lt;/p>
&lt;p>UI → PlanetManager → événement → objets intéressés&lt;/p>
&lt;p>Exemple :&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>Cela permet :&lt;/p>
&lt;ul>
&lt;li>moins de calculs inutiles&lt;/li>
&lt;li>un code plus lisible&lt;/li>
&lt;li>un débogage plus simple&lt;/li>
&lt;li>de meilleures performances en VR&lt;/li>
&lt;/ul>
&lt;h3 id="7--pourquoi-cest-encore-plus-important-en-xr">7 — Pourquoi c’est encore plus important en XR&lt;/h3>
&lt;p>En VR et XR :&lt;/p>
&lt;ul>
&lt;li>les builds sont plus longs&lt;/li>
&lt;li>les bugs sont plus difficiles à diagnostiquer&lt;/li>
&lt;li>les performances sont critiques&lt;/li>
&lt;/ul>
&lt;p>Une architecture claire permet :&lt;/p>
&lt;ul>
&lt;li>de tester certaines parties sans casque&lt;/li>
&lt;li>de comprendre rapidement un bug&lt;/li>
&lt;li>de modifier une interaction sans casser tout le projet&lt;/li>
&lt;/ul>
&lt;h3 id="8---scriptableobject">8 - ScriptableObject&lt;/h3>
&lt;p>Un &lt;strong>ScriptableObject&lt;/strong> est un asset Unity utilisé pour stocker des &lt;strong>paramètres éditables&lt;/strong>.&lt;/p>
&lt;p>Contrairement à un &lt;code>MonoBehaviour&lt;/code>, il n’est pas attaché à un GameObject.&lt;/p>
&lt;p>Exemple dans ce TP :&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>On crée ensuite l’asset dans 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>Puis on l’assigne dans l’inspecteur.&lt;/p>
&lt;p>Intérêt :&lt;/p>
&lt;ul>
&lt;li>configuration centralisée&lt;/li>
&lt;li>modifiable sans changer le code&lt;/li>
&lt;li>partageable entre scènes.&lt;/li>
&lt;/ul>
&lt;h3 id="ce-que-vous-devez-retenir">Ce que vous devez retenir&lt;/h3>
&lt;p>Pendant ce TP, essayez de toujours vous poser ces questions :&lt;/p>
&lt;ul>
&lt;li>Est-ce que ce script fait trop de choses ?&lt;/li>
&lt;li>Est-ce que ce code dépend inutilement d’un autre objet ?&lt;/li>
&lt;li>Est-ce que je peux comprendre ce système rapidement ?&lt;/li>
&lt;li>Est-ce que je pourrais déboguer ça facilement sur un casque VR ?&lt;/li>
&lt;/ul>
&lt;p>Si la réponse est &amp;ldquo;non&amp;rdquo;, il faut probablement simplifier ou mieux séparer les responsabilités.&lt;/p>
&lt;hr>
&lt;h2 id="étape-1--boot-xr--scène-minimale-tag--v10--45-min">Étape 1 — Boot XR + scène minimale (Tag : v1.0) — ~45 min&lt;/h2>
&lt;h3 id="objectifs">Objectifs&lt;/h3>
&lt;ul>
&lt;li>Avoir une scène VR &lt;strong>qui tourne&lt;/strong> et un pipeline de build &lt;strong>déboguable&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h3 id="à-faire">À faire&lt;/h3>
&lt;ol>
&lt;li>Créer le projet Unity.&lt;/li>
&lt;li>Installer et activer :
&lt;ul>
&lt;li>OpenXR&lt;/li>
&lt;li>XR Interaction Toolkit
&lt;ul>
&lt;li>Vous pouvez ajouter les Starter Assets (pour avoir un XR Rig complet), et le XR Simulator (pour tester sans casque) depuis les samples du package.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Input System&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Configurer le projet :
&lt;ul>
&lt;li>Activer les plugin-provider nécessaires pour votre casque (OpenXR, Oculus, etc.) dans Player Settings / XR Plug-in Management.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Ajouter un rig VR (XRI) :
&lt;ul>
&lt;li>XR Origin&lt;/li>
&lt;li>XR Interaction Manager&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Créer une scène &lt;code>Boot&lt;/code> :
&lt;ul>
&lt;li>sol + repère (axes X/Y/Z par exemple pour voir l&amp;rsquo;origine du monde dans la scène)&lt;/li>
&lt;li>un cube interactable (grab) pour valider XRI&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Configurer le build &lt;strong>debug&lt;/strong> :
&lt;ul>
&lt;li>Development Build&lt;/li>
&lt;li>Script Debugging&lt;/li>
&lt;li>(si possible) Autoconnect Profiler&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="rendu-attendu">Rendu attendu&lt;/h3>
&lt;ul>
&lt;li>Capture (mode miroir dans le casque ou XR simulator) montrant le cube grab en VR&lt;/li>
&lt;li>Repo taggé &lt;code>v1.0-boot&lt;/code> (n&amp;rsquo;noubliez pas le &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="étape-2--architecture--modèles-services-contrôleurs">Étape 2 — Architecture : modèles, services, contrôleurs&lt;/h2>
&lt;p>Tag : &lt;strong>v1.1-architecture&lt;/strong> — ~1h15&lt;/p>
&lt;h3 id="objectif-1">Objectif&lt;/h3>
&lt;p>Avant d’ajouter de la VR, des interactions et de l’UI, nous allons &lt;strong>structurer correctement l’application&lt;/strong>.&lt;/p>
&lt;p>Dans les projets XR, les problèmes viennent souvent de :&lt;/p>
&lt;ul>
&lt;li>scripts qui se référencent dans tous les sens&lt;/li>
&lt;li>logique mélangée avec l’affichage&lt;/li>
&lt;li>dépendances invisibles&lt;/li>
&lt;li>code impossible à tester sans casque.&lt;/li>
&lt;/ul>
&lt;p>Nous allons donc construire une architecture simple :&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--organisation-des-dossiers">1 — Organisation des dossiers&lt;/h3>
&lt;p>Créer les dossiers suivants :&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>But :&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dossier&lt;/th>
&lt;th>Rôle&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Bootstrap&lt;/td>
&lt;td>point d’entrée&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Models&lt;/td>
&lt;td>état de la simulation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Services&lt;/td>
&lt;td>calculs&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>GameObjects Unity&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Config&lt;/td>
&lt;td>paramètres éditables&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="2--le-modèle-du-temps">2 — Le modèle du temps&lt;/h3>
&lt;p>Dans notre simulation, la variable centrale est &lt;strong>le temps simulé&lt;/strong>.&lt;/p>
&lt;p>Créer :&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>ce script &lt;strong>ne dépend pas de Unity&lt;/strong>&lt;/li>
&lt;li>il ne contient &lt;strong>que l’état et les événements&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h3 id="3--configuration-du-système-solaire">3 — Configuration du système solaire&lt;/h3>
&lt;p>Certains paramètres doivent être éditables dans Unity.&lt;/p>
&lt;p>Créer :&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>Créer ensuite l’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--utilisation-des-données-astronomiques-fournies">4 — Utilisation des données astronomiques fournies&lt;/h3>
&lt;p>Le TP fournit déjà une classe :&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>Elle contient :&lt;/p>
&lt;ul>
&lt;li>les paramètres orbitaux&lt;/li>
&lt;li>les calculs d’orbite&lt;/li>
&lt;li>la fonction :&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>⚠️ Cette classe est &lt;strong>la source officielle des positions des planètes&lt;/strong>.
Les étudiants &lt;strong>ne doivent pas modifier ce fichier&lt;/strong>.&lt;/p>
&lt;h3 id="5--service-daccès-aux-positions-planétaires">5 — Service d’accès aux positions planétaires&lt;/h3>
&lt;p>Même si la fonction existe déjà, on passe par un &lt;strong>service&lt;/strong>.&lt;/p>
&lt;p>Pourquoi ?&lt;/p>
&lt;ul>
&lt;li>garder une architecture propre&lt;/li>
&lt;li>pouvoir remplacer l’implémentation plus tard&lt;/li>
&lt;li>faciliter les tests.&lt;/li>
&lt;/ul>
&lt;p>Créer :&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>Implémentation :&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>Ce service agit comme une &lt;strong>couche d’abstraction&lt;/strong>.&lt;/p>
&lt;h3 id="6--vue-planète">6 — Vue planète&lt;/h3>
&lt;p>Créer :&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>Rôle :&lt;/p>
&lt;ul>
&lt;li>représenter une planète dans la scène&lt;/li>
&lt;li>appliquer les positions calculées.&lt;/li>
&lt;/ul>
&lt;h3 id="7--controller-du-système-solaire">7 — Controller du système solaire&lt;/h3>
&lt;p>Le controller relie :&lt;/p>
&lt;ul>
&lt;li>le modèle&lt;/li>
&lt;li>le service&lt;/li>
&lt;li>les vues.&lt;/li>
&lt;/ul>
&lt;p>Créer :&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>Responsabilités :&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Élément&lt;/th>
&lt;th>Rôle&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Model&lt;/td>
&lt;td>état de la simulation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Service&lt;/td>
&lt;td>calcul des positions&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>affichage.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="8--bootstrapper">8 — Bootstrapper&lt;/h3>
&lt;p>Créer :&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>Rôle :&lt;/p>
&lt;ul>
&lt;li>créer les objets principaux&lt;/li>
&lt;li>connecter les dépendances&lt;/li>
&lt;li>démarrer la simulation.&lt;/li>
&lt;/ul>
&lt;p>C’est ce qu’on appelle le &lt;strong>composition root&lt;/strong>.&lt;/p>
&lt;h3 id="9--mise-en-place-dans-la-scène">9 — Mise en place dans la scène&lt;/h3>
&lt;p>Créer un 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>Ajouter :&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>Dans l’inspecteur :&lt;/p>
&lt;ul>
&lt;li>assigner &lt;code>SolarSystemConfig&lt;/code>&lt;/li>
&lt;li>assigner les &lt;code>PlanetView&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Structure possible :&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>Chaque planète possède :&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--test-rapide">10 — Test rapide&lt;/h3>
&lt;p>Lancer la scène.&lt;/p>
&lt;p>Résultat attendu :&lt;/p>
&lt;ul>
&lt;li>les planètes apparaissent&lt;/li>
&lt;li>elles sont positionnées correctement&lt;/li>
&lt;li>log &lt;code>[BOOT]&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Si rien ne se passe :&lt;/p>
&lt;p>Checklist :&lt;/p>
&lt;ul>
&lt;li>les PlanetView sont assignées&lt;/li>
&lt;li>le bootstrapper est actif&lt;/li>
&lt;li>la scène est sauvegardée.&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Si vous suivez bien les étapes, vous devriez constater un petit bug. Modifiez le service pour le corriger.&lt;/p>
&lt;/blockquote>
&lt;h3 id="11--vérification-de-larchitecture">11 — Vérification de l’architecture&lt;/h3>
&lt;p>Votre code doit respecter :&lt;/p>
&lt;ul>
&lt;li>pas de &lt;code>FindObjectOfType&lt;/code>&lt;/li>
&lt;li>pas de &lt;code>GameObject.Find&lt;/code>&lt;/li>
&lt;li>dépendances visibles&lt;/li>
&lt;li>modèle indépendant de Unity&lt;/li>
&lt;li>logique séparée de l’affichage.&lt;/li>
&lt;/ul>
&lt;h3 id="12--schéma-de-larchitecture">12 — Schéma de l’architecture&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="résultat-attendu">Résultat attendu&lt;/h3>
&lt;p>À la fin de cette étape :&lt;/p>
&lt;ul>
&lt;li>l’application est structurée&lt;/li>
&lt;li>les planètes utilisent &lt;code>PlanetData&lt;/code>&lt;/li>
&lt;li>les dépendances sont explicites&lt;/li>
&lt;li>la simulation est pilotée par un modèle.&lt;/li>
&lt;/ul>
&lt;p>Tag Git :&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="étape-3--positionnement-des-planètes--événements">Étape 3 — Positionnement des planètes + événements&lt;/h2>
&lt;p>Tag : &lt;strong>v1.2-events&lt;/strong> — ~1h15&lt;/p>
&lt;h3 id="objectif-2">Objectif&lt;/h3>
&lt;p>Mettre en place le &lt;strong>flux principal de la simulation&lt;/strong> :&lt;/p>
&lt;ul>
&lt;li>le temps change,&lt;/li>
&lt;li>un événement est déclenché,&lt;/li>
&lt;li>le contrôleur met à jour les planètes.&lt;/li>
&lt;/ul>
&lt;p>On évite volontairement de faire tourner toute la logique dans &lt;code>Update()&lt;/code>.&lt;/p>
&lt;p>Flux attendu :&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>On a déjà créé la plupart de ces éléments dans l’étape précédente, il s’agit maintenant de les connecter correctement avec des événements.&lt;/p>
&lt;p>Responsabilités :&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Élément&lt;/th>
&lt;th>Rôle&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Model&lt;/td>
&lt;td>état du temps&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Service&lt;/td>
&lt;td>calcul des positions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controller&lt;/td>
&lt;td>applique les positions&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View&lt;/td>
&lt;td>déplace les objets&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="1--déclencher-via-un-événement">1 — Déclencher via un événement&lt;/h3>
&lt;blockquote>
&lt;p>Ceci a déjà été implémenté dans l’étape précédente, mais il est important de comprendre comment cela fonctionne.&lt;/p>
&lt;/blockquote>
&lt;p>Dans &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>Dans le 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>Ainsi les planètes se mettent à jour &lt;strong>uniquement quand le temps change&lt;/strong>.&lt;/p>
&lt;h3 id="2--faire-avancer-le-temps">2 — Faire avancer le temps&lt;/h3>
&lt;p>Créer un petit composant 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">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>Dans &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--vérification">3 — Vérification&lt;/h3>
&lt;p>Quand la scène démarre :&lt;/p>
&lt;p>Console :&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>Et les planètes doivent se déplacer.&lt;/p>
&lt;p>Checklist :&lt;/p>
&lt;ul>
&lt;li>aucun &lt;code>FindObjectOfType&lt;/code>&lt;/li>
&lt;li>aucun &lt;code>GameObject.Find&lt;/code>&lt;/li>
&lt;li>la mise à jour passe par &lt;strong>OnTimeChanged&lt;/strong>&lt;/li>
&lt;li>les &lt;code>PlanetView&lt;/code> sont assignées.&lt;/li>
&lt;/ul>
&lt;h3 id="optionnel-trajectoires">(Optionnel) Trajectoires&lt;/h3>
&lt;p>Pour afficher une orbite :&lt;/p>
&lt;ul>
&lt;li>ajouter un &lt;strong>LineRenderer&lt;/strong>&lt;/li>
&lt;li>calculer plusieurs positions&lt;/li>
&lt;li>ne pas recalculer chaque frame.&lt;/li>
&lt;/ul>
&lt;p>Principe :&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="résultat-attendu-1">Résultat attendu&lt;/h3>
&lt;p>À la fin :&lt;/p>
&lt;ul>
&lt;li>la simulation est pilotée par &lt;code>TimeModel&lt;/code>&lt;/li>
&lt;li>les planètes suivent correctement le temps&lt;/li>
&lt;li>la mise à jour passe par l’événement &lt;code>OnTimeChanged&lt;/code>&lt;/li>
&lt;li>l’architecture reste claire et testable.&lt;/li>
&lt;/ul>
&lt;p>Dans le &lt;strong>rapport&lt;/strong>, vous devez également inclure :&lt;/p>
&lt;ul>
&lt;li>un schéma simple du flux &lt;code>TimeModel → Controller → PlanetView&lt;/code>&lt;/li>
&lt;li>une courte explication du fonctionnement&lt;/li>
&lt;li>une capture de l’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="étape-4--interactions-vr--grab--focus--échelle">Étape 4 — Interactions VR : grab / focus / échelle&lt;/h2>
&lt;p>Tag : &lt;strong>v1.3-vr-interactions&lt;/strong> — ~1h30&lt;/p>
&lt;h3 id="objectif-3">Objectif&lt;/h3>
&lt;p>Dans cette étape, vous allez transformer votre simulation en &lt;strong>véritable objet manipulable en VR&lt;/strong>.&lt;/p>
&lt;p>L’utilisateur doit pouvoir :&lt;/p>
&lt;ul>
&lt;li>déplacer le système solaire,&lt;/li>
&lt;li>modifier son échelle,&lt;/li>
&lt;li>sélectionner une planète pour obtenir des informations.&lt;/li>
&lt;/ul>
&lt;p>Le but n’est pas seulement de faire fonctionner ces interactions, mais de &lt;strong>les intégrer proprement dans l’architecture du projet&lt;/strong>.&lt;/p>
&lt;p>Les interactions XR doivent déclencher des &lt;strong>intentions&lt;/strong>, qui sont ensuite appliquées par un système clair (controller / manager), et non manipuler directement tous les objets de la scène.&lt;/p>
&lt;p>Flux recommandé :&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">Script d’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">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--préparer-un-tabletop-manipulable">1 — Préparer un &amp;ldquo;tabletop&amp;rdquo; manipulable&lt;/h3>
&lt;p>Pour faciliter les interactions, vous devez organiser la scène autour d’un objet racine manipulable.&lt;/p>
&lt;p>Créer une structure proche de :&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> devient l’objet que vous allez :&lt;/p>
&lt;ul>
&lt;li>déplacer&lt;/li>
&lt;li>faire tourner&lt;/li>
&lt;li>mettre à l’échelle.&lt;/li>
&lt;/ul>
&lt;p>Toutes les planètes doivent être enfants de cet objet.&lt;/p>
&lt;p>Pourquoi ?&lt;/p>
&lt;ul>
&lt;li>cela simplifie énormément les interactions,&lt;/li>
&lt;li>cela évite de modifier chaque planète individuellement,&lt;/li>
&lt;li>cela permet de garder un point central pour les contrôleurs.&lt;/li>
&lt;/ul>
&lt;h3 id="2--interaction-grab">2 — Interaction Grab&lt;/h3>
&lt;h4 id="objectif-4">Objectif&lt;/h4>
&lt;p>Permettre à l’utilisateur de &lt;strong>prendre le système solaire et le déplacer&lt;/strong> dans l’espace.&lt;/p>
&lt;h5 id="à-faire-1">À faire&lt;/h5>
&lt;p>Créer un objet servant de &lt;strong>poignée de manipulation&lt;/strong>.&lt;/p>
&lt;p>Ajouter un interactable XR permettant :&lt;/p>
&lt;ul>
&lt;li>de saisir l’objet avec le contrôleur,&lt;/li>
&lt;li>de déplacer l’ensemble du système.&lt;/li>
&lt;/ul>
&lt;p>Quelques éléments nécessaires :&lt;/p>
&lt;ul>
&lt;li>collider&lt;/li>
&lt;li>rigidbody (souvent kinematic)&lt;/li>
&lt;li>composant d’interaction XR.&lt;/li>
&lt;/ul>
&lt;p>Test attendu :&lt;/p>
&lt;ul>
&lt;li>l’utilisateur peut attraper l’objet,&lt;/li>
&lt;li>le déplacer,&lt;/li>
&lt;li>le relâcher.&lt;/li>
&lt;/ul>
&lt;h5 id="bonnes-pratiques">Bonnes pratiques&lt;/h5>
&lt;ul>
&lt;li>ne pas attacher cette interaction directement aux planètes,&lt;/li>
&lt;li>garder un objet dédié (handle),&lt;/li>
&lt;li>ajouter des logs utiles :&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--contrôle-de-léchelle">3 — Contrôle de l’échelle&lt;/h3>
&lt;h4 id="objectif-5">Objectif&lt;/h4>
&lt;p>Permettre à l’utilisateur de &lt;strong>zoomer / dézoomer&lt;/strong> le système solaire.&lt;/p>
&lt;p>L’échelle doit :&lt;/p>
&lt;ul>
&lt;li>rester dans des limites raisonnables,&lt;/li>
&lt;li>être centralisée dans un seul script,&lt;/li>
&lt;li>produire des logs utiles.&lt;/li>
&lt;/ul>
&lt;h5 id="à-implémenter">À implémenter&lt;/h5>
&lt;p>Créer un système responsable de :&lt;/p>
&lt;ul>
&lt;li>recevoir une valeur de scale,&lt;/li>
&lt;li>la limiter si nécessaire,&lt;/li>
&lt;li>appliquer la transformation à &lt;code>SolarSystemRoot&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>Vous pouvez par exemple créer un composant dédié :&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>Ce composant pourrait exposer une méthode du type :&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>C’est ce composant qui applique réellement l’échelle.&lt;/p>
&lt;h5 id="interaction-utilisateur">Interaction utilisateur&lt;/h5>
&lt;p>Vous êtes libres de choisir comment modifier l’échelle :&lt;/p>
&lt;ul>
&lt;li>slider UI en VR&lt;/li>
&lt;li>bouton +&lt;/li>
&lt;li>rotation d’un knob&lt;/li>
&lt;li>geste à deux mains (bonus)&lt;/li>
&lt;/ul>
&lt;p>Mais dans tous les cas :&lt;/p>
&lt;ul>
&lt;li>l’interaction ne doit pas manipuler directement le transform&lt;/li>
&lt;li>elle appelle un contrôleur.&lt;/li>
&lt;/ul>
&lt;h5 id="logs-attendus">Logs attendus&lt;/h5>
&lt;p>Exemples :&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--sélection-dune-planète">4 — Sélection d’une planète&lt;/h3>
&lt;h4 id="objectif-6">Objectif&lt;/h4>
&lt;p>Permettre à l’utilisateur de &lt;strong>pointer une planète avec un rayon XR&lt;/strong> et de la sélectionner.&lt;/p>
&lt;p>Lorsqu’une planète est sélectionnée :&lt;/p>
&lt;ul>
&lt;li>un événement doit être déclenché,&lt;/li>
&lt;li>le système doit savoir &lt;strong>quelle planète est active&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;h5 id="mise-en-place">Mise en place&lt;/h5>
&lt;p>Chaque planète doit pouvoir :&lt;/p>
&lt;ul>
&lt;li>être détectée par un ray interactor,&lt;/li>
&lt;li>signaler sa sélection.&lt;/li>
&lt;/ul>
&lt;p>Vous pouvez par exemple créer un script du type :&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>Ce script peut :&lt;/p>
&lt;ul>
&lt;li>référencer la &lt;code>PlanetView&lt;/code>,&lt;/li>
&lt;li>déclencher un événement ou appeler un système central.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Important :&lt;/strong> le script de sélection ne doit pas contenir toute la logique de focus ou d’UI.&lt;/p>
&lt;p>Il signale seulement &lt;em>une planète a été sélectionnée&lt;/em>.&lt;/p>
&lt;h3 id="5--mode-focus">5 — Mode Focus&lt;/h3>
&lt;h4 id="objectif-7">Objectif&lt;/h4>
&lt;p>Quand une planète est sélectionnée :&lt;/p>
&lt;ul>
&lt;li>la planète doit être &amp;ldquo;mise en avant&amp;rdquo; (ex. centrée, agrandie, etc.),&lt;/li>
&lt;li>un panneau d’information apparaît.&lt;/li>
&lt;/ul>
&lt;h5 id="à-implémenter-1">À implémenter&lt;/h5>
&lt;p>Créer un système responsable du focus.&lt;/p>
&lt;p>Par exemple :&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>Responsabilités possibles :&lt;/p>
&lt;ul>
&lt;li>déplacer l’origine XR ou la caméra,&lt;/li>
&lt;li>activer un panneau d’information,&lt;/li>
&lt;li>enregistrer la planète active.&lt;/li>
&lt;/ul>
&lt;p>Le panneau peut afficher par exemple :&lt;/p>
&lt;ul>
&lt;li>nom de la planète&lt;/li>
&lt;li>distance au soleil&lt;/li>
&lt;li>période orbitale&lt;/li>
&lt;li>temps simulé actuel.&lt;/li>
&lt;/ul>
&lt;p>Le contenu exact est libre.&lt;/p>
&lt;h3 id="6--organisation-recommandée">6 — Organisation recommandée&lt;/h3>
&lt;p>Une organisation possible des responsabilités :&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Élément&lt;/th>
&lt;th>Rôle&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>XR Interactable&lt;/td>
&lt;td>capter l’interaction&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Script d’interaction&lt;/td>
&lt;td>traduire l’action utilisateur&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Controller&lt;/td>
&lt;td>appliquer la logique&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View&lt;/td>
&lt;td>afficher le résultat&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Exemple de flux :&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--vérifications">7 — Vérifications&lt;/h3>
&lt;p>Avant de passer à l’étape suivante, vérifiez :&lt;/p>
&lt;h5 id="grab">Grab&lt;/h5>
&lt;ul>
&lt;li>le système solaire peut être déplacé&lt;/li>
&lt;li>le mouvement est stable&lt;/li>
&lt;li>des logs apparaissent.&lt;/li>
&lt;/ul>
&lt;h5 id="scale">Scale&lt;/h5>
&lt;ul>
&lt;li>l’échelle peut changer&lt;/li>
&lt;li>les limites sont respectées&lt;/li>
&lt;li>les transformations restent cohérentes.&lt;/li>
&lt;/ul>
&lt;h5 id="focus">Focus&lt;/h5>
&lt;ul>
&lt;li>la sélection fonctionne avec le rayon&lt;/li>
&lt;li>la bonne planète est détectée&lt;/li>
&lt;li>un feedback visuel apparaît.&lt;/li>
&lt;/ul>
&lt;h3 id="résultat-attendu-2">Résultat attendu&lt;/h3>
&lt;p>À la fin de cette étape, l’utilisateur peut :&lt;/p>
&lt;ul>
&lt;li>manipuler le système solaire,&lt;/li>
&lt;li>modifier son échelle,&lt;/li>
&lt;li>sélectionner une planète,&lt;/li>
&lt;li>voir des informations associées.&lt;/li>
&lt;/ul>
&lt;p>L’architecture doit rester :&lt;/p>
&lt;ul>
&lt;li>lisible,&lt;/li>
&lt;li>découplée,&lt;/li>
&lt;li>déboguable.&lt;/li>
&lt;/ul>
&lt;p>Tag Git :&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="étape-5--ui-world-space--instrumentation-tag--v14--1h00">Étape 5 — UI world-space + instrumentation (Tag : v1.4) — ~1h00&lt;/h2>
&lt;h3 id="objectifs-1">Objectifs&lt;/h3>
&lt;p>Rendre l’application pilotable et déboguable &lt;em>dans le casque&lt;/em>.&lt;/p>
&lt;h3 id="à-faire-2">À faire&lt;/h3>
&lt;h4 id="a-ui-world-space">A) UI world-space&lt;/h4>
&lt;p>Créer un panneau VR permettant au minimum :&lt;/p>
&lt;ul>
&lt;li>Date (ou scrub)&lt;/li>
&lt;li>Play/pause + vitesse (x1, x10, x100…)&lt;/li>
&lt;li>Toggle trajectoires&lt;/li>
&lt;li>Reset viewpoint / reset scale&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Le panneau UI peut être un Canvas world-space interactif avec le XR Interaction Toolkit.&lt;/p>
&lt;/blockquote>
&lt;h4 id="b-debugoverlay-obligatoire">B) DebugOverlay (obligatoire)&lt;/h4>
&lt;p>Créer un overlay discret affichant :&lt;/p>
&lt;ul>
&lt;li>FPS / frame time (approx)&lt;/li>
&lt;li>Date actuelle + vitesse&lt;/li>
&lt;li>Dernière action utilisateur&lt;/li>
&lt;li>Warnings clés (scale hors bornes, perf suspecte, event spam)&lt;/li>
&lt;/ul>
&lt;p>Le DebugOverlay peut être un simple panneau TextMeshPro mis à jour périodiquement.&lt;/p>
&lt;p>Unity permet de récupérer tous les logs (Debug.Log, warnings, erreurs) via :&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>Vous pouvez utiliser ce callback pour :&lt;/p>
&lt;ul>
&lt;li>récupérer les messages importants&lt;/li>
&lt;li>garder les derniers logs&lt;/li>
&lt;li>les afficher dans l’overlay.&lt;/li>
&lt;/ul>
&lt;h5 id="exemple-1">Exemple&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-logs-structurés">C) Logs structurés&lt;/h4>
&lt;p>Tous les événements critiques doivent loguer au 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="rendu-attendu-1">Rendu attendu&lt;/h3>
&lt;ul>
&lt;li>Capture du panneau UI + overlay debug visibles en VR&lt;/li>
&lt;li>Tag &lt;code>v1.4-ui-debug&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="étape-6--debugging-xr-guidé-tag--v15--optionnel">Étape 6 — Debugging XR guidé (Tag : v1.5) — (Optionnel)&lt;/h2>
&lt;h3 id="objectifs-2">Objectifs&lt;/h3>
&lt;p>Apprendre une méthode de debug XR reproductible.&lt;/p>
&lt;h3 id="travail-demandé">Travail demandé&lt;/h3>
&lt;p>Vous devez traiter au moins &lt;strong>2 tickets&lt;/strong> parmi les 3 suivants (selon starter fourni par l’enseignant) :&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ticket A — Input qui marche en Editor mais pas sur casque&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Ticket B — Après reload scène, les events se déclenchent en double&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Ticket C — Échelle/repères incohérents : objets “loin” ou “immenses”&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>Pour chaque ticket :&lt;/p>
&lt;ul>
&lt;li>fournir un &lt;strong>repro minimal&lt;/strong>&lt;/li>
&lt;li>décrire la &lt;strong>méthode&lt;/strong> (instrumentation → hypothèse → test → fix)&lt;/li>
&lt;li>ajouter un test simple de non-régression (ex. log + guard + assert)&lt;/li>
&lt;/ul>
&lt;h3 id="rendu-attendu-2">Rendu attendu&lt;/h3>
&lt;ul>
&lt;li>Section “Debug story” dans README&lt;/li>
&lt;li>Tag &lt;code>v1.5-debug-tickets&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="étape-7--performance-xr-tag--v20--optionnel">Étape 7 — Performance XR (Tag : v2.0) — (Optionnel)&lt;/h2>
&lt;h3 id="objectifs-3">Objectifs&lt;/h3>
&lt;p>Identifier et corriger un problème perf (CPU/GPU/GC) typique VR.&lt;/p>
&lt;h3 id="à-faire-3">À faire&lt;/h3>
&lt;ol>
&lt;li>Mesurer (Profiler) :
&lt;ul>
&lt;li>CPU main thread&lt;/li>
&lt;li>GC alloc&lt;/li>
&lt;li>scripts en Update&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Choisir &lt;strong>un goulot&lt;/strong> et l’améliorer :
&lt;ul>
&lt;li>caching trajectoires&lt;/li>
&lt;li>pooling&lt;/li>
&lt;li>réduction fréquence d’update&lt;/li>
&lt;li>suppression d’allocations&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Prouver l’amélioration :
&lt;ul>
&lt;li>capture du profiler avant/après&lt;/li>
&lt;li>décrire le changement (2–5 lignes)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h3 id="rendu-attendu-3">Rendu attendu&lt;/h3>
&lt;ul>
&lt;li>Section “Perf story” dans README&lt;/li>
&lt;li>Tag &lt;code>v2.0-final&lt;/code>&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="bonus-facultatifs">Bonus (facultatifs)&lt;/h2>
&lt;ul>
&lt;li>Rotation propre des planètes (axiale + orbitale)&lt;/li>
&lt;li>Skybox / voûte céleste&lt;/li>
&lt;li>Mode “surface de planète” (point de vue, constellations)&lt;/li>
&lt;li>Téléportation + confort (snap turn, vignette)&lt;/li>
&lt;li>Mini “debug console” in-VR avec filtres par catégories&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="critères-dévaluation-indicatif">Critères d’évaluation (indicatif)&lt;/h2>
&lt;ul>
&lt;li>Architecture &amp;amp; propreté — 40%&lt;/li>
&lt;li>Interactions VR — 30%&lt;/li>
&lt;li>Observabilité — 20%&lt;/li>
&lt;/ul>
&lt;p>Bonus :&lt;/p>
&lt;ul>
&lt;li>Debugging (tickets + méthode)&lt;/li>
&lt;li>Performance (mesure + amélioration)&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="checklist-rapide-avant-rendu">Checklist rapide (avant rendu)&lt;/h2>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Repo public/privé accessible, tags présents&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> App fonctionne sur casque&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> UI world-space OK&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> DebugOverlay OK&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> README avec captures + schémas&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Pas de &lt;code>FindObjectOfType&lt;/code> / &lt;code>GameObject.Find&lt;/code> dans le code final&lt;/li>
&lt;/ul></description></item></channel></rss>