using System.Linq; using System.Threading.Tasks; using UnityEngine; namespace MyInfoMate.Vr.Scene { /// /// Un personnage posé dans la scène : GLB chargé au runtime, animation idle en /// boucle, orientation vers le visiteur. /// /// Pas de lip sync, jamais — écarté au niveau produit. Un personnage stylisé qui /// respire et regarde dans la bonne direction porte l'essentiel de la présence ; /// une bouche mal synchronisée la détruit. /// public class PersonaInstance : MonoBehaviour { [SerializeField] Transform lookAtTarget; [SerializeField] bool gazeAtVisitor = true; [SerializeField] float gazeDegreesPerSecond = 90f; Quaternion _restRotation; public async Task LoadAsync(string url, Transform visitorHead) { lookAtTarget = visitorHead; _restRotation = transform.localRotation; var model = await GltfLoader.LoadAsync(url, transform, "Model"); if (model == null) return false; PlayIdle(model); return true; } /// /// ⚠️ glTFast expose les animations de deux façons selon /// GltfImportSettings.AnimationMethod : Legacy (composant /// , le défaut) ou Mecanim (un Animator et /// son controller). Ce code suit le défaut ; si le personnage reste figé alors /// que le GLB contient bien des clips, c'est le premier endroit à regarder. /// /// On joue le clip nommé /// « idle » s'il existe, sinon le premier — un pack d'animations tiers ne /// respecte aucune convention de nommage, et refuser de jouer parce que le /// nom ne colle pas donnerait un personnage figé sans message d'erreur. /// void PlayIdle(GameObject model) { var animation = model.GetComponentInChildren(); if (animation == null) return; var clip = animation.Cast() .FirstOrDefault(s => s.name.ToLowerInvariant().Contains("idle")) ?? animation.Cast().FirstOrDefault(); if (clip == null) { Debug.LogWarning($"[Persona] {name} : aucune animation dans le GLB"); return; } clip.wrapMode = WrapMode.Loop; animation.Play(clip.name); } void Update() { if (!gazeAtVisitor || lookAtTarget == null) return; // Rotation sur Y seulement : un personnage qui bascule la tête vers un // visiteur assis ou grand donne un effet de pantin. var direction = lookAtTarget.position - transform.position; direction.y = 0f; if (direction.sqrMagnitude < 0.01f) return; transform.rotation = Quaternion.RotateTowards( transform.rotation, Quaternion.LookRotation(direction), gazeDegreesPerSecond * Time.deltaTime); } public void ReturnToRest() => transform.localRotation = _restRotation; } }