TP — XR Engineering & Debugging (VR) — Solar System Workbench

Développer une application VR structurée, instrumentée et déboguable, en appliquant des bonnes pratiques d’ingénierie Unity/XR.

Objectif

Ce TP (~7h) a pour objectif de vous apprendre à développer et maintenir une application VR avec :

  • une architecture claire (séparation des responsabilités, dépendances explicites),
  • une chaîne d’itération efficace (tester un maximum sans casque, builds debug),
  • une observabilité minimale (overlay VR, logs structurés),
  • des pratiques de debugging XR,
  • une introduction au profiling VR (optionnel selon le temps restant).

L’application développée est un atelier VR de visualisation du système solaire : un modèle “tabletop” manipulable, une UI en world-space, des modes de vue, et des contrôles temporels.


Pré-requis

Compétences

  • Bases C# / Unity (scènes, prefabs, scripts)
  • Notions Git (commit, tag, push)
  • Bases VR (confort, world-space UI) utiles mais pas indispensables

Outils

  • Unity LTS (2022.3 ou 2023 LTS)
  • Packages Unity :
    • OpenXR
    • XR Interaction Toolkit (XRI)
    • Input System
  • Un casque VR compatible OpenXR

Ressources

  • PlanetData.cs (fournit les données et fonctions de calcul orbital)

Règles d’ingénierie (à respecter)

Dans ce TP, vous devez explicitement appliquer les règles suivantes :

  1. Aucune dépendance implicite

    • Pas de FindObjectOfType, pas de GameObject.Find, pas de dépendance “magique”.
    • Les références nécessaires sont injectées via l’inspecteur ou via un bootstrapper.
  2. Pas de logique métier dans Update() par défaut

    • Update() est réservé aux besoins continus réellement nécessaires.
    • Préférer événements, actions, timers contrôlés.
  3. Séparation des responsabilités

    • Modèle de données / services (calculs, temps, conversion) ≠ vues (GameObjects) ≠ contrôleurs.
  4. Debuggabilité

    • Tout comportement critique doit produire des logs exploitables.
    • Mise en place d’un DebugOverlay visible dans le casque.
  5. Performance (optionnel)

    • Interdiction d’allocations évitables par frame (strings/LINQ).
    • Profiling obligatoire (preuve avant/après sur un point).

Scénario : Solar System VR Workbench

Vous développerez une application VR contenant :

  • un “tabletop” du système solaire (échelle manipulable),
  • des planètes positionnées à un instant t (les fonctions de calcul sont déjà fournies par PlanetData),
  • un contrôle du temps (date + vitesse),
  • des trajectoires activables/désactivables,
  • un mode “focus planète” (centrage/inspection),
  • une UI VR (world-space) pour piloter l’application,
  • un overlay debug (FPS, état, derniers événements, warnings).

Organisation du TP

Le TP est structuré en 5 étapes principales et 2 extensions optionnelles.

Obligatoire :

1 — Boot XR
2 — Architecture
3 — Événements / simulation
4 — Interactions VR
5 — UI + Debug overlay

Optionnel (si temps restant) :

6 — Debugging XR guidé
7 — Optimisation de performance


Organisation du rendu (Git)

Vous devez livrer :

  • un repo Git (lien),
  • des tags Git à chaque étape (v1.0, v1.1, … v2.0),
  • un court compte-rendu (README ou PDF) :
    • captures d’écran (dans le casque ou miroir),
    • fonctionnalités implémentées,
    • schéma d’architecture (simple, lisible),
    • une “debug story” (si vous avez traité l’étape 6),
    • une “perf story” (si vous avez traité l’étape 7).

Rappel tags

À chaque étape :

  • git add .
  • git commit -m "…"
  • git tag -a v1.x -m "…"
  • git push origin main
  • git push origin --tags

Concepts utiles pour ce TP

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.

1 — Logique métier

La logique métier correspond aux règles qui définissent comment fonctionne votre application, indépendamment de l’affichage.

Exemples dans ce TP :

Logique métier :

  • calculer la position d’une planète à une date donnée
  • changer la vitesse du temps
  • activer ou désactiver les trajectoires
  • changer l’échelle du système solaire

Affichage (pas logique métier) :

  • déplacer un GameObject
  • changer un texte dans l’UI
  • afficher un panneau
Mauvais exemple
void Update()
{
    planet.transform.position = PlanetData.ComputePosition(DateTime.Now);
}

Le calcul et l’affichage sont mélangés.

Bon exemple
DateTime currentTime;

void OnTimeChanged(DateTime t)
{
    currentTime = t;
    UpdateView();
}

La logique décide quand on met à jour, la vue décide comment on affiche.

2 — Séparation des responsabilités

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.

On sépare donc les rôles.

Structure simple utilisée dans ce TP

Data / Model ←→ Services ←→ Controllers ←→ Views

Model (données)

Contient uniquement des données.

Exemple :

TimeModel

  • currentTime
  • timeSpeed

Un modèle ne dépend normalement pas de Unity.

Services

Contiennent des calculs ou des règles.

Exemple :

PlanetEphemerisService

  • computePosition()
Controllers

Coordonnent l’application.

Ils reçoivent des événements et mettent à jour les vues.

Exemple :

SolarSystemController

Views

Objets Unity visibles.

Exemples :

  • PlanetView
  • OrbitRenderer
  • UI panels
Résumé
  • Model → stocker l’état
  • Service → calculer
  • Controller → décider quoi faire
  • View → afficher

3 — Bootstrapper

Le problème

Dans beaucoup de projets Unity on trouve :

  • FindObjectOfType
  • GameObject.Find
  • des Singletons utilisés partout

Ces méthodes fonctionnent mais rendent le projet fragile.

Solution simple

Créer un objet qui initialise l’application.

C’est ce qu’on appelle un Bootstrapper.

Exemple :

AppBootstrapper

Son rôle :

  • créer les services
  • connecter les objets
  • initialiser les données
Exemple simple
public class AppBootstrapper : MonoBehaviour
{
    public SolarSystemController controller;

    void Start()
    {
        var timeModel = new TimeModel();
        controller.Init(timeModel);
    }
}

Les dépendances deviennent visibles et contrôlées.

4 — Événements

Les événements permettent à différents objets de communiquer sans être fortement couplés.

Sans événements :

UI → Manager → Controller → Camera

Tout dépend de tout.

Avec événements :

UI → Event → Objets intéressés

Chaque système reste indépendant.

Exemple
public event Action<DateTime> OnTimeChanged;

Quand la date change :

OnTimeChanged?.Invoke(newTime);

Un autre script peut écouter cet événement :

void Start()
{
    planetManager.OnTimeChanged += UpdatePosition;
}

5 — Actions

Une Action est simplement une fonction stockée dans une variable.

Exemple :

Action<DateTime> callback;

Dans Unity on les utilise pour :

  • événements
  • callbacks
  • interactions UI

6 — Gestion de l’état de la simulation

Dans cette application, la variable centrale n’est pas vraiment un “timer” technique. Ce qui compte est simplement la date de la simulation.

Autrement dit, on manipule un état global :

Date simulée → positions des planètes → affichage

Lorsque cette date change :

  • les positions des planètes doivent être recalculées
  • certaines visualisations doivent être mises à jour
  • l’interface peut afficher la nouvelle valeur

Plutôt que de recalculer en permanence dans Update(), on déclenche un événement uniquement quand l’état change.

Mauvaise approche

void Update()
{
    UpdatePlanets(currentDate);
}

Chaque objet vérifie en permanence si quelque chose a changé.

Approche recommandée

On utilise un événement qui signale un changement d’état.

public event Action<DateTime> OnTimeChanged;

Quand la date change :

UI → PlanetManager → événement → objets intéressés

Exemple :

void Start()
{
    PlanetManager.current.OnTimeChanged += UpdatePosition;
}

Cela permet :

  • moins de calculs inutiles
  • un code plus lisible
  • un débogage plus simple
  • de meilleures performances en VR

7 — Pourquoi c’est encore plus important en XR

En VR et XR :

  • les builds sont plus longs
  • les bugs sont plus difficiles à diagnostiquer
  • les performances sont critiques

Une architecture claire permet :

  • de tester certaines parties sans casque
  • de comprendre rapidement un bug
  • de modifier une interaction sans casser tout le projet

8 - ScriptableObject

Un ScriptableObject est un asset Unity utilisé pour stocker des paramètres éditables.

Contrairement à un MonoBehaviour, il n’est pas attaché à un GameObject.

Exemple dans ce TP :

[CreateAssetMenu(menuName = "XR/Solar System Config")]
public class SolarSystemConfig : ScriptableObject
{
    public float distanceScale;
    public float planetSizeScale;
    public bool showOrbits;
}

On crée ensuite l’asset dans Unity :

Assets → Create → XR → Solar System Config

Puis on l’assigne dans l’inspecteur.

Intérêt :

  • configuration centralisée
  • modifiable sans changer le code
  • partageable entre scènes.

Ce que vous devez retenir

Pendant ce TP, essayez de toujours vous poser ces questions :

  • Est-ce que ce script fait trop de choses ?
  • Est-ce que ce code dépend inutilement d’un autre objet ?
  • Est-ce que je peux comprendre ce système rapidement ?
  • Est-ce que je pourrais déboguer ça facilement sur un casque VR ?

Si la réponse est “non”, il faut probablement simplifier ou mieux séparer les responsabilités.


Étape 1 — Boot XR + scène minimale (Tag : v1.0) — ~45 min

Objectifs

  • Avoir une scène VR qui tourne et un pipeline de build déboguable.

À faire

  1. Créer le projet Unity.
  2. Installer et activer :
    • OpenXR
    • XR Interaction Toolkit
      • 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.
    • Input System
  3. Configurer le projet :
    • Activer les plugin-provider nécessaires pour votre casque (OpenXR, Oculus, etc.) dans Player Settings / XR Plug-in Management.
  4. Ajouter un rig VR (XRI) :
    • XR Origin
    • XR Interaction Manager
  5. Créer une scène Boot :
    • sol + repère (axes X/Y/Z par exemple pour voir l’origine du monde dans la scène)
    • un cube interactable (grab) pour valider XRI
  6. Configurer le build debug :
    • Development Build
    • Script Debugging
    • (si possible) Autoconnect Profiler

Rendu attendu

  • Capture (mode miroir dans le casque ou XR simulator) montrant le cube grab en VR
  • Repo taggé v1.0-boot (n’noubliez pas le .gitignore)

Étape 2 — Architecture : modèles, services, contrôleurs

Tag : v1.1-architecture — ~1h15

Objectif

Avant d’ajouter de la VR, des interactions et de l’UI, nous allons structurer correctement l’application.

Dans les projets XR, les problèmes viennent souvent de :

  • scripts qui se référencent dans tous les sens
  • logique mélangée avec l’affichage
  • dépendances invisibles
  • code impossible à tester sans casque.

Nous allons donc construire une architecture simple :

Bootstrapper
Models / Config
Services
Controllers
Views (Unity)

1 — Organisation des dossiers

Créer les dossiers suivants :

Scripts
 ├── Bootstrap
 ├── Models
 ├── Services
 ├── Controllers
 ├── Views
 └── Config

But :

DossierRôle
Bootstrappoint d’entrée
Modelsétat de la simulation
Servicescalculs
Controllerscoordination
ViewsGameObjects Unity
Configparamètres éditables

2 — Le modèle du temps

Dans notre simulation, la variable centrale est le temps simulé.

Créer :

Scripts/Models/TimeModel.cs
using System;

public class TimeModel
{
    public DateTime CurrentTime { get; private set; }

    public float TimeScale { get; private set; } = 1f;

    public bool IsPlaying { get; private set; } = true;

    public event Action<DateTime> OnTimeChanged;

    public void SetTime(DateTime t)
    {
        CurrentTime = t;
        OnTimeChanged?.Invoke(CurrentTime);
    }

    public void SetScale(float scale)
    {
        TimeScale = scale;
    }

    public void Play()
    {
        IsPlaying = true;
    }

    public void Pause()
    {
        IsPlaying = false;
    }
}

Important :

  • ce script ne dépend pas de Unity
  • il ne contient que l’état et les événements.

3 — Configuration du système solaire

Certains paramètres doivent être éditables dans Unity.

Créer :

Scripts/Config/SolarSystemConfig.cs
using UnityEngine;

[CreateAssetMenu(menuName = "XR/Solar System Config")]
public class SolarSystemConfig : ScriptableObject
{
    public float distanceScale = 0.000001f;
    public float planetSizeScale = 0.01f;
    public bool showOrbits = true;
}

Créer ensuite l’asset :

Assets → Create → XR → Solar System Config

4 — Utilisation des données astronomiques fournies

Le TP fournit déjà une classe :

PlanetData.cs

Elle contient :

  • les paramètres orbitaux
  • les calculs d’orbite
  • la fonction :
PlanetData.GetPlanetPosition(PlanetData.Planet planet, DateTime time)

⚠️ Cette classe est la source officielle des positions des planètes. Les étudiants ne doivent pas modifier ce fichier.

5 — Service d’accès aux positions planétaires

Même si la fonction existe déjà, on passe par un service.

Pourquoi ?

  • garder une architecture propre
  • pouvoir remplacer l’implémentation plus tard
  • faciliter les tests.

Créer :

Scripts/Services/IPlanetEphemerisService.cs
using System;
using UnityEngine;

public interface IPlanetEphemerisService
{
    Vector3 GetPlanetPosition(PlanetData.Planet planet, DateTime date);
}

Implémentation :

Scripts/Services/PlanetEphemerisService.cs
using System;
using UnityEngine;

public class PlanetEphemerisService : IPlanetEphemerisService
{
    public Vector3 GetPlanetPosition(PlanetData.Planet planet, DateTime date)
    {
        return PlanetData.GetPlanetPosition(planet, date);
    }
}

Ce service agit comme une couche d’abstraction.

6 — Vue planète

Créer :

Scripts/Views/PlanetView.cs
using UnityEngine;

public class PlanetView : MonoBehaviour
{
    public PlanetData.Planet planet;

    public void SetPosition(Vector3 pos)
    {
        transform.localPosition = pos;
    }
}

Rôle :

  • représenter une planète dans la scène
  • appliquer les positions calculées.

7 — Controller du système solaire

Le controller relie :

  • le modèle
  • le service
  • les vues.

Créer :

Scripts/Controllers/PlanetSystemController.cs
using System;
using UnityEngine;

public class PlanetSystemController
{
    TimeModel timeModel;
    IPlanetEphemerisService ephemeris;

    PlanetView[] planets;

    public PlanetSystemController(
        TimeModel timeModel,
        IPlanetEphemerisService ephemeris,
        PlanetView[] planets)
    {
        this.timeModel = timeModel;
        this.ephemeris = ephemeris;
        this.planets = planets;

        timeModel.OnTimeChanged += UpdatePlanets;
    }

    void UpdatePlanets(DateTime time)
    {
        Debug.Log("[TIME] Updating planets " + time);

        foreach (var planet in planets)
        {
            Vector3 pos =
                ephemeris.GetPlanetPosition(planet.planet, time);

            planet.SetPosition(pos);
        }
    }
}

Responsabilités :

ÉlémentRôle
Modelétat de la simulation
Servicecalcul des positions
Controllercoordination
Viewaffichage.

8 — Bootstrapper

Créer :

Scripts/Bootstrap/AppBootstrapper.cs
using UnityEngine;
using System;

public class AppBootstrapper : MonoBehaviour
{
    public SolarSystemConfig config;

    public PlanetView[] planets;

    TimeModel timeModel;
    PlanetSystemController controller;

    void Start()
    {
        Debug.Log("[BOOT] Initializing application");

        timeModel = new TimeModel();

        var ephemeris = new PlanetEphemerisService();

        controller = new PlanetSystemController(
            timeModel,
            ephemeris,
            planets
        );

        timeModel.SetTime(DateTime.Now);
    }
}

Rôle :

  • créer les objets principaux
  • connecter les dépendances
  • démarrer la simulation.

C’est ce qu’on appelle le composition root.

9 — Mise en place dans la scène

Créer un GameObject :

App

Ajouter :

AppBootstrapper

Dans l’inspecteur :

  • assigner SolarSystemConfig
  • assigner les PlanetView.

Structure possible :

App
SolarSystem
 ├ Sun
 ├ Mercury
 ├ Venus
 ├ Earth
 ├ Mars
 ├ Jupiter
 ├ Saturn
 ├ Uranus
 └ Neptune

Chaque planète possède :

PlanetView

10 — Test rapide

Lancer la scène.

Résultat attendu :

  • les planètes apparaissent
  • elles sont positionnées correctement
  • log [BOOT].

Si rien ne se passe :

Checklist :

  • les PlanetView sont assignées
  • le bootstrapper est actif
  • la scène est sauvegardée.

Si vous suivez bien les étapes, vous devriez constater un petit bug. Modifiez le service pour le corriger.

11 — Vérification de l’architecture

Votre code doit respecter :

  • pas de FindObjectOfType
  • pas de GameObject.Find
  • dépendances visibles
  • modèle indépendant de Unity
  • logique séparée de l’affichage.

12 — Schéma de l’architecture

Views

Controllers

Services

Models and Config

Bootstrap

creates

reads

creates

injects

assigns refs

implements

uses

uses

updates

calls GetPlanetPosition

attached to

OnTimeChanged

SetPosition

AppBootstrapper - MonoBehaviour

TimeModel - model

SolarSystemConfig - ScriptableObject

IPlanetEphemerisService interface

PlanetEphemerisService implementation

PlanetData static data

PlanetSystemController controller

PlanetView array MonoBehaviour

Planet GameObjects

creates

creates

creates

references inspector

references asset

subscribes

uses

implements

calls

updates

AppBootstrapper

+SolarSystemConfig config

+PlanetView[] planets

-TimeModel timeModel

-PlanetSystemController controller

+Start()

TimeModel

+DateTime CurrentTime

+float TimeScale

+bool IsPlaying

+OnTimeChanged : ActionDateTime

+SetTime(DateTime)

+SetScale(float)

+Play()

+Pause()

SolarSystemConfig

+float distanceScale

+float planetSizeScale

+bool showOrbits

IPlanetEphemerisService

+GetPlanetPosition(Planet, DateTime) : Vector3

PlanetEphemerisService

+GetPlanetPosition(Planet, DateTime) : Vector3

PlanetData

+GetPlanetPosition(Planet, DateTime) : Vector3

PlanetSystemController

-TimeModel timeModel

-IPlanetEphemerisService ephemeris

-PlanetView[] planets

+PlanetSystemController(TimeModel, IPlanetEphemerisService, PlanetView[])

PlanetView

+Planet planet

+SetPosition(Vector3)

Résultat attendu

À la fin de cette étape :

  • l’application est structurée
  • les planètes utilisent PlanetData
  • les dépendances sont explicites
  • la simulation est pilotée par un modèle.

Tag Git :

v1.1-architecture

Étape 3 — Positionnement des planètes + événements

Tag : v1.2-events — ~1h15

Objectif

Mettre en place le flux principal de la simulation :

  • le temps change,
  • un événement est déclenché,
  • le contrôleur met à jour les planètes.

On évite volontairement de faire tourner toute la logique dans Update().

Flux attendu :

TimeController
TimeModel
OnTimeChanged
PlanetSystemController
PlanetView

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.

Responsabilités :

ÉlémentRôle
Modelétat du temps
Servicecalcul des positions
Controllerapplique les positions
Viewdéplace les objets

1 — Déclencher via un événement

Ceci a déjà été implémenté dans l’étape précédente, mais il est important de comprendre comment cela fonctionne.

Dans TimeModel :

public void SetTime(DateTime t)
{
    CurrentTime = t;
    OnTimeChanged?.Invoke(CurrentTime);
}

Dans le controller :

timeModel.OnTimeChanged += UpdatePlanets;

Ainsi les planètes se mettent à jour uniquement quand le temps change.

2 — Faire avancer le temps

Créer un petit composant Unity :

TimeController
public class TimeController : MonoBehaviour
{
    public float secondsPerDay = 1f;

    TimeModel model;
    DateTime current;

    public void Init(TimeModel m)
    {
        model = m;
        current = DateTime.Now;
        model.SetTime(current);
    }

    void Update()
    {
        if (!model.IsPlaying) return;

        current = current.AddDays(Time.deltaTime * secondsPerDay);
        model.SetTime(current);
    }
}

Dans AppBootstrapper :

timeController.Init(timeModel);

3 — Vérification

Quand la scène démarre :

Console :

[BOOT] Initializing application
[TIME] Updating planets ...

Et les planètes doivent se déplacer.

Checklist :

  • aucun FindObjectOfType
  • aucun GameObject.Find
  • la mise à jour passe par OnTimeChanged
  • les PlanetView sont assignées.

(Optionnel) Trajectoires

Pour afficher une orbite :

  • ajouter un LineRenderer
  • calculer plusieurs positions
  • ne pas recalculer chaque frame.

Principe :

for (int i = 0; i < samples; i++)
{
    var t = start.AddDays(i * 10);
    points[i] = ephemeris.GetPlanetPosition(planet, t);
}

Résultat attendu

À la fin :

  • la simulation est pilotée par TimeModel
  • les planètes suivent correctement le temps
  • la mise à jour passe par l’événement OnTimeChanged
  • l’architecture reste claire et testable.

Dans le rapport, vous devez également inclure :

  • un schéma simple du flux TimeModel → Controller → PlanetView
  • une courte explication du fonctionnement
  • une capture de l’application.

Tag :

v1.2-events

Étape 4 — Interactions VR : grab / focus / échelle

Tag : v1.3-vr-interactions — ~1h30

Objectif

Dans cette étape, vous allez transformer votre simulation en véritable objet manipulable en VR.

L’utilisateur doit pouvoir :

  • déplacer le système solaire,
  • modifier son échelle,
  • sélectionner une planète pour obtenir des informations.

Le but n’est pas seulement de faire fonctionner ces interactions, mais de les intégrer proprement dans l’architecture du projet.

Les interactions XR doivent déclencher des intentions, qui sont ensuite appliquées par un système clair (controller / manager), et non manipuler directement tous les objets de la scène.

Flux recommandé :

XR Interaction
Script d’interaction
Controller
Views

1 — Préparer un “tabletop” manipulable

Pour faciliter les interactions, vous devez organiser la scène autour d’un objet racine manipulable.

Créer une structure proche de :

SolarSystemRoot
 ├ SolarSystem
 │   ├ Sun
 │   ├ Mercury
 │   ├ Venus
 │   └ ...
 └ Handle

SolarSystemRoot devient l’objet que vous allez :

  • déplacer
  • faire tourner
  • mettre à l’échelle.

Toutes les planètes doivent être enfants de cet objet.

Pourquoi ?

  • cela simplifie énormément les interactions,
  • cela évite de modifier chaque planète individuellement,
  • cela permet de garder un point central pour les contrôleurs.

2 — Interaction Grab

Objectif

Permettre à l’utilisateur de prendre le système solaire et le déplacer dans l’espace.

À faire

Créer un objet servant de poignée de manipulation.

Ajouter un interactable XR permettant :

  • de saisir l’objet avec le contrôleur,
  • de déplacer l’ensemble du système.

Quelques éléments nécessaires :

  • collider
  • rigidbody (souvent kinematic)
  • composant d’interaction XR.

Test attendu :

  • l’utilisateur peut attraper l’objet,
  • le déplacer,
  • le relâcher.
Bonnes pratiques
  • ne pas attacher cette interaction directement aux planètes,
  • garder un objet dédié (handle),
  • ajouter des logs utiles :
[XR] Table grabbed
[XR] Table released

3 — Contrôle de l’échelle

Objectif

Permettre à l’utilisateur de zoomer / dézoomer le système solaire.

L’échelle doit :

  • rester dans des limites raisonnables,
  • être centralisée dans un seul script,
  • produire des logs utiles.
À implémenter

Créer un système responsable de :

  • recevoir une valeur de scale,
  • la limiter si nécessaire,
  • appliquer la transformation à SolarSystemRoot.

Vous pouvez par exemple créer un composant dédié :

ScaleController

Ce composant pourrait exposer une méthode du type :

SetScale(float value)

C’est ce composant qui applique réellement l’échelle.

Interaction utilisateur

Vous êtes libres de choisir comment modifier l’échelle :

  • slider UI en VR
  • bouton +
  • rotation d’un knob
  • geste à deux mains (bonus)

Mais dans tous les cas :

  • l’interaction ne doit pas manipuler directement le transform
  • elle appelle un contrôleur.
Logs attendus

Exemples :

[INPUT] Scale requested
[XR] Scale applied
[WARN] Scale clamped

4 — Sélection d’une planète

Objectif

Permettre à l’utilisateur de pointer une planète avec un rayon XR et de la sélectionner.

Lorsqu’une planète est sélectionnée :

  • un événement doit être déclenché,
  • le système doit savoir quelle planète est active.
Mise en place

Chaque planète doit pouvoir :

  • être détectée par un ray interactor,
  • signaler sa sélection.

Vous pouvez par exemple créer un script du type :

PlanetSelectable

Ce script peut :

  • référencer la PlanetView,
  • déclencher un événement ou appeler un système central.

Important : le script de sélection ne doit pas contenir toute la logique de focus ou d’UI.

Il signale seulement une planète a été sélectionnée.

5 — Mode Focus

Objectif

Quand une planète est sélectionnée :

  • la planète doit être “mise en avant” (ex. centrée, agrandie, etc.),
  • un panneau d’information apparaît.
À implémenter

Créer un système responsable du focus.

Par exemple :

FocusController

Responsabilités possibles :

  • déplacer l’origine XR ou la caméra,
  • activer un panneau d’information,
  • enregistrer la planète active.

Le panneau peut afficher par exemple :

  • nom de la planète
  • distance au soleil
  • période orbitale
  • temps simulé actuel.

Le contenu exact est libre.

6 — Organisation recommandée

Une organisation possible des responsabilités :

ÉlémentRôle
XR Interactablecapter l’interaction
Script d’interactiontraduire l’action utilisateur
Controllerappliquer la logique
Viewafficher le résultat

Exemple de flux :

Ray Interactor
PlanetSelectable
PlanetSelectionSystem
FocusController
UI

7 — Vérifications

Avant de passer à l’étape suivante, vérifiez :

Grab
  • le système solaire peut être déplacé
  • le mouvement est stable
  • des logs apparaissent.
Scale
  • l’échelle peut changer
  • les limites sont respectées
  • les transformations restent cohérentes.
Focus
  • la sélection fonctionne avec le rayon
  • la bonne planète est détectée
  • un feedback visuel apparaît.

Résultat attendu

À la fin de cette étape, l’utilisateur peut :

  • manipuler le système solaire,
  • modifier son échelle,
  • sélectionner une planète,
  • voir des informations associées.

L’architecture doit rester :

  • lisible,
  • découplée,
  • déboguable.

Tag Git :

v1.3-vr-interactions

Étape 5 — UI world-space + instrumentation (Tag : v1.4) — ~1h00

Objectifs

Rendre l’application pilotable et déboguable dans le casque.

À faire

A) UI world-space

Créer un panneau VR permettant au minimum :

  • Date (ou scrub)
  • Play/pause + vitesse (x1, x10, x100…)
  • Toggle trajectoires
  • Reset viewpoint / reset scale

Le panneau UI peut être un Canvas world-space interactif avec le XR Interaction Toolkit.

B) DebugOverlay (obligatoire)

Créer un overlay discret affichant :

  • FPS / frame time (approx)
  • Date actuelle + vitesse
  • Dernière action utilisateur
  • Warnings clés (scale hors bornes, perf suspecte, event spam)

Le DebugOverlay peut être un simple panneau TextMeshPro mis à jour périodiquement.

Unity permet de récupérer tous les logs (Debug.Log, warnings, erreurs) via :

Application.logMessageReceived

Vous pouvez utiliser ce callback pour :

  • récupérer les messages importants
  • garder les derniers logs
  • les afficher dans l’overlay.
Exemple
using UnityEngine;
using TMPro;
using System.Collections.Generic;

public class DebugOverlay : MonoBehaviour
{
    public TextMeshProUGUI text;

    Queue<string> lines = new Queue<string>();

    public int maxLines = 15;

    void OnEnable()
    {
        Application.logMessageReceived += HandleLog;
    }

    void OnDisable()
    {
        Application.logMessageReceived -= HandleLog;
    }

    void HandleLog(string logString, string stackTrace, LogType type)
    {
        lines.Enqueue(logString);

        while (lines.Count > maxLines)
            lines.Dequeue();

        text.text = string.Join("\n", lines);
    }
}

C) Logs structurés

Tous les événements critiques doivent loguer au format :

  • [TIME] …
  • [INPUT] …
  • [XR] …
  • [PERF] …

Rendu attendu

  • Capture du panneau UI + overlay debug visibles en VR
  • Tag v1.4-ui-debug

Étape 6 — Debugging XR guidé (Tag : v1.5) — (Optionnel)

Objectifs

Apprendre une méthode de debug XR reproductible.

Travail demandé

Vous devez traiter au moins 2 tickets parmi les 3 suivants (selon starter fourni par l’enseignant) :

  • Ticket A — Input qui marche en Editor mais pas sur casque
  • Ticket B — Après reload scène, les events se déclenchent en double
  • Ticket C — Échelle/repères incohérents : objets “loin” ou “immenses”

Pour chaque ticket :

  • fournir un repro minimal
  • décrire la méthode (instrumentation → hypothèse → test → fix)
  • ajouter un test simple de non-régression (ex. log + guard + assert)

Rendu attendu

  • Section “Debug story” dans README
  • Tag v1.5-debug-tickets

Étape 7 — Performance XR (Tag : v2.0) — (Optionnel)

Objectifs

Identifier et corriger un problème perf (CPU/GPU/GC) typique VR.

À faire

  1. Mesurer (Profiler) :
    • CPU main thread
    • GC alloc
    • scripts en Update
  2. Choisir un goulot et l’améliorer :
    • caching trajectoires
    • pooling
    • réduction fréquence d’update
    • suppression d’allocations
  3. Prouver l’amélioration :
    • capture du profiler avant/après
    • décrire le changement (2–5 lignes)

Rendu attendu

  • Section “Perf story” dans README
  • Tag v2.0-final

Bonus (facultatifs)

  • Rotation propre des planètes (axiale + orbitale)
  • Skybox / voûte céleste
  • Mode “surface de planète” (point de vue, constellations)
  • Téléportation + confort (snap turn, vignette)
  • Mini “debug console” in-VR avec filtres par catégories

Critères d’évaluation (indicatif)

  • Architecture & propreté — 40%
  • Interactions VR — 30%
  • Observabilité — 20%

Bonus :

  • Debugging (tickets + méthode)
  • Performance (mesure + amélioration)

Checklist rapide (avant rendu)

  • Repo public/privé accessible, tags présents
  • App fonctionne sur casque
  • UI world-space OK
  • DebugOverlay OK
  • README avec captures + schémas
  • Pas de FindObjectOfType / GameObject.Find dans le code final