Premier commit du cinquième front. Trois parties : - `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205), un APK unique pour tous les clients. Menu flottant à sélection au regard, appairage, chargement de scène GLB, POI, cache de contenu, télémétrie. - `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec les pièges rencontrés consignés dans son README. - `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript, three.js), partagé avec les autres fronts. - `docs/` : état des lieux, setup Unity, décisions d'architecture et plan d'exécution en 9 étapes. La scène est décrite par un `scene.json` poussé par `adb push` : l'app le préfère à celui embarqué dans l'APK. Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par Git LFS dès ce premier commit — les y faire entrer après coup demanderait de réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake (`Library/`, `.utmp/`, Burst debug) sont ignorés. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
83 lines
3.2 KiB
C#
83 lines
3.2 KiB
C#
using System.Threading.Tasks;
|
|
using MyInfoMate.Vr.Scene;
|
|
using UnityEngine;
|
|
|
|
namespace MyInfoMate.Vr.Boot
|
|
{
|
|
/// <summary>
|
|
/// Orchestration de l'étape S1 : décor, calibration, personnage, zone de navigation.
|
|
///
|
|
/// <b>Provisoire par construction.</b> Les chemins sont ici en dur, parce que S1
|
|
/// n'a ni manifeste ni réseau — c'est exactement son intérêt : prouver la chaîne
|
|
/// technique sans backend. S2 remplace ce script par <c>SceneBuilder</c>, qui lit
|
|
/// les mêmes assets depuis un manifeste. Ne rien construire au-dessus de celui-ci.
|
|
/// </summary>
|
|
public class S1Bootstrap : MonoBehaviour
|
|
{
|
|
[Header("Fichiers dans Assets/StreamingAssets")]
|
|
[SerializeField] string worldFile = "world.glb";
|
|
[SerializeField] string personaFile = "persona.glb";
|
|
|
|
[Header("Placement du personnage — coordonnées glTF, en mètres")]
|
|
[SerializeField] float[] personaPosition = { -1.5f, 0f, -1f };
|
|
|
|
[Header("Navigation")]
|
|
[SerializeField] float navigationRadiusMeters = 3f;
|
|
[SerializeField] bool showBoundary = true;
|
|
[SerializeField] bool runCalibration = true;
|
|
|
|
Transform _sceneRoot;
|
|
|
|
async void Start()
|
|
{
|
|
_sceneRoot = new GameObject("SceneRoot").transform;
|
|
_sceneRoot.SetParent(transform, false);
|
|
|
|
var head = Camera.main != null ? Camera.main.transform : null;
|
|
if (head == null)
|
|
Debug.LogError("[S1] Aucune caméra active : le rig VR n'est pas dans la scène. " +
|
|
"Meta → Tools → Building Blocks → Camera Rig.");
|
|
|
|
await LoadWorld();
|
|
|
|
if (runCalibration)
|
|
await gameObject.AddComponent<CalibrationCheck>().RunAsync(_sceneRoot);
|
|
|
|
await LoadPersona(head);
|
|
|
|
SetUpBounds(head);
|
|
}
|
|
|
|
async Task LoadWorld()
|
|
{
|
|
var world = await GltfLoader.LoadAsync(
|
|
GltfLoader.StreamingAssetsUrl(worldFile), _sceneRoot, "World");
|
|
|
|
if (world == null)
|
|
Debug.LogError($"[S1] {worldFile} absent de StreamingAssets — " +
|
|
"exporte un monde Marble en mesh GLB et dépose-le là.");
|
|
}
|
|
|
|
async Task LoadPersona(Transform head)
|
|
{
|
|
var holder = new GameObject("Persona");
|
|
holder.transform.SetParent(_sceneRoot, false);
|
|
holder.transform.localPosition = GltfSpace.Position(personaPosition);
|
|
|
|
var persona = holder.AddComponent<PersonaInstance>();
|
|
if (!await persona.LoadAsync(GltfLoader.StreamingAssetsUrl(personaFile), head))
|
|
Debug.LogError($"[S1] {personaFile} absent de StreamingAssets.");
|
|
}
|
|
|
|
void SetUpBounds(Transform head)
|
|
{
|
|
// La zone suit le rig, pas la tête : c'est la position au sol du visiteur
|
|
// qui compte, et la tête bouge de 60 cm quand on se penche.
|
|
var rig = head != null ? head.root : null;
|
|
var bounds = new GameObject("NavigationBounds").AddComponent<NavigationBounds>();
|
|
bounds.transform.SetParent(_sceneRoot, false);
|
|
bounds.Configure(rig, head, navigationRadiusMeters, showBoundary);
|
|
}
|
|
}
|
|
}
|