using MyInfoMate.Vr.Net; using MyInfoMate.Vr.Scene; using UnityEngine; namespace MyInfoMate.Vr.Boot { /// /// Items E2 et E3 du lot XR-4 : le casque s'appaire, puis lit le contenu /// de sa configuration. Rien d'immersif ici — c'est la preuve que la chaîne /// réseau tient, affichée en une phrase dans le casque. /// /// ⚠️ Ne pas confondre avec et : /// ceux-là sont la piste Scène 3D (S0-S8), celui-ci le canal VR (E0-E11). Deux /// numérotations, deux plans. /// /// Le code PIN ne se saisit pas encore : sans l'Interaction SDK (item E1), /// il n'y a ni pointeur ni clavier. Il se pose donc dans l'inspecteur avant le /// build ; l'écran d'appairage arrive avec E5. /// public class PairingBootstrap : MonoBehaviour { [Header("Serveur")] [SerializeField] string baseUrl = "https://api.mymuseum.be"; [Header("Appairage — code PIN de l'instance")] [SerializeField] string pinCode = ""; [Header("Nom affiché dans l'onglet XR du manager")] [SerializeField] string headsetName = "Quest"; [Header("Langue d'affichage — l'export les porte toutes")] [SerializeField] string language = "FR"; [Header("Réappairer même si ce casque l'est déjà")] [SerializeField] bool forgetPairing; async void Start() { var head = Camera.main != null ? Camera.main.transform : null; if (head == null) Debug.LogError("[Pairing] Aucune caméra active : le rig VR n'est pas dans la " + "scène. Meta → Tools → Building Blocks → Camera Rig."); if (forgetPairing) PairingService.Forget(); var pairing = PairingService.Restore(); if (pairing == null) { if (string.IsNullOrEmpty(pinCode)) { SceneMessage.Show("Ce casque n'est pas appairé. Renseignez le code PIN " + "du lieu dans l'inspecteur, puis relancez.", head); return; } var result = await new PairingService() .PairAsync(baseUrl, pinCode, headsetName); if (!result.Ok) { SceneMessage.Show(result.Error, head); return; } pairing = result.Value; Debug.Log($"[Pairing] Casque appairé — device {pairing.DeviceId}, " + $"instance {pairing.InstanceId}"); } var client = new ApiClient(pairing.BaseUrl, pairing.ApiKey); var content = new ContentService(client, pairing.ConfigurationId); var loaded = await content.LoadAsync(); if (!loaded.Ok) { SceneMessage.Show(loaded.Error, head); return; } Show(loaded.Export, head, loaded.FromCache ? loaded.CachedAt : null); // E4 : le cache a déjà été affiché, le réseau ne fait plus attendre personne. // Un échec ici ne se voit pas — c'est le comportement voulu pour une borne. if (loaded.FromCache) { var fresh = await content.RefreshAsync(); if (fresh != null) { Debug.Log("[Content] Contenu mis à jour depuis le serveur."); Show(fresh, head, null); } } } SceneMessage _message; void Show(ConfigurationExport export, Transform head, System.DateTime? cachedAt) { // Le rafraîchissement de fond réaffiche : sans ça, deux panneaux se // superposent et le texte devient illisible. if (_message != null) Destroy(_message.gameObject); var roots = export.RootSections(); var lines = $"{export.Label} — {roots.Count} sections"; if (cachedAt.HasValue) lines += $"\n(hors ligne — contenu du {cachedAt.Value.ToLocalTime():dd/MM HH:mm})"; for (var i = 0; i < roots.Count; i++) { var title = ConfigurationExport.Translate(roots[i].Title, language) ?? roots[i].Label; Debug.Log($"[Export] {roots[i].Type} — {title}"); // Le message flottant reste lisible à cinq lignes ; la console a le reste. if (i < 5) lines += $"\n{title} ({roots[i].Type})"; } _message = SceneMessage.Show(lines, head); } } }