using System.Collections; using TMPro; using UnityEngine; // Le bandeau qui explique la page affichée. // // À chaque page son message. Quand l'une s'affiche, le guide prend son texte et // monte de sa position de repos à sa position d'affichage ; quand la page courante // n'a rien à dire, il redescend se ranger. // // Le guide se surveille lui-même : il lit _G.PNL, que ShowPNL écrit à chaque // changement de page. Il suffit donc de le poser dans le Canvas et de remplir sa // liste, sans rien brancher. // // Hiérarchie attendue : // Guide <- ce script + RectTransform // Text (TMP) <- le message, trouvé tout seul si le champ est vide [RequireComponent(typeof(RectTransform))] [DisallowMultipleComponent] public class Guide : MonoBehaviour { [System.Serializable] public class Entry { [Tooltip("Le nom de la page surveillée, tel qu'écrit dans _G.PNL par ShowPNL. Ex : INSPIRATION")] public string page; [Tooltip("La clé dans _T.TRANS, pour un message traduit. Vide = le message ci-dessous est affiché tel quel.")] public string key; [TextArea(2, 4)] [Tooltip("Le message affiché tant que cette page est ouverte. Sert aussi de secours si la clé est introuvable.")] public string message; } [Header("Références (vide = trouvées automatiquement)")] [Tooltip("Le bandeau déplacé. Vide = cet objet.")] [SerializeField] private RectTransform banner; [Tooltip("L'étiquette à remplir. Vide = premier TMP_Text trouvé sous le guide.")] [SerializeField] private TMP_Text label; [Header("Positions")] [Tooltip("Abscisse du bandeau, la même au repos et à l'affichage.")] [SerializeField] private float x; [Tooltip("Ordonnée au repos, hors de vue.")] [SerializeField] private float hiddenY = -225f; [Tooltip("Ordonnée une fois affiché.")] [SerializeField] private float shownY; [Header("Animation")] [Tooltip("Durée du déplacement, en secondes. 0 = pas d'animation.")] [SerializeField] private float duration = 0.35f; [Tooltip("Allure du déplacement, de 0 à 1.")] [SerializeField] private AnimationCurve ease = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f); [Header("Messages")] [Tooltip("Un message par page. La première page du nom cherché l'emporte.")] [SerializeField] private Entry[] messages; // La page dont le message est affiché. Sert à ne réagir qu'aux changements : // sans elle, l'animation repartirait à chaque image. private string _page; private Entry _current; // La langue affichée. Le dropdown peut la changer en cours de route : le texte // se remplace alors sur place, sans rejouer l'animation. private int _language; private Coroutine _sliding; public bool IsShown { get; private set; } // Le message affiché, pour qui veut le relire. public string Message => label != null ? label.text : ""; private RectTransform Banner { get { if (banner == null) banner = (RectTransform)transform; return banner; } } private TMP_Text Label { get { if (label == null) label = GetComponentInChildren(true); return label; } } void OnEnable() { // Le guide reprend au repos : la page ouverte sera reconnue à la première // image et le fera monter, message compris. _page = null; _current = null; _language = _G.L; IsShown = false; Place(hiddenY); } void OnDisable() { StopSliding(); } void Update() { if (messages == null || messages.Length == 0) return; if (_G.PNL != _page) { _page = _G.PNL; _current = EntryFor(_page); _language = _G.L; if (_current == null) Hide(); else Show(TextOf(_current)); return; } if (_language != _G.L) { _language = _G.L; if (_current != null && Label != null) Label.text = TextOf(_current); } } // Le message de la page nommée, ou rien si elle n'en a pas. private Entry EntryFor(string page) { if (string.IsNullOrEmpty(page)) return null; foreach (Entry entry in messages) { if (entry != null && entry.page == page) return entry; } return null; } // Le texte dans la langue courante. La clé prime, le message écrit à la main // sert de secours : une clé oubliée laisse un texte lisible plutôt qu'un // morceau de la table pris au hasard. private static string TextOf(Entry entry) { if (string.IsNullOrEmpty(entry.key)) return entry.message; int ind = System.Array.IndexOf(_T.TRANS, entry.key); if (ind < 0 || ind + _G.L >= _T.TRANS.Length) return entry.message; return _T.TRANS[ind + _G.L]; } // Affiche un message et fait monter le guide. // // L'animation repart toujours du repos, même si le guide est déjà en haut : une // page qui s'ouvre se signale, sinon le texte changerait sans que l'œil le // remarque. public void Show(string message) { if (Label != null) Label.text = message; IsShown = true; Slide(hiddenY, shownY); } // Range le guide, sans toucher au message : il ne clignote pas en partant. public void Hide() { if (!IsShown && _sliding == null) { Place(hiddenY); return; } IsShown = false; Slide(Banner.anchoredPosition.y, hiddenY); } private void Slide(float fromY, float toY) { StopSliding(); // Hors du Play, ou sans durée, le déplacement est immédiat : une coroutine // ne tournerait pas dans l'éditeur. if (duration <= 0f || !Application.isPlaying || !isActiveAndEnabled) { Place(toY); return; } Place(fromY); _sliding = StartCoroutine(Sliding(fromY, toY)); } private IEnumerator Sliding(float fromY, float toY) { float elapsed = 0f; while (elapsed < duration) { elapsed += Time.deltaTime; float progress = Mathf.Clamp01(elapsed / duration); Place(Mathf.LerpUnclamped(fromY, toY, ease.Evaluate(progress))); yield return null; } Place(toY); _sliding = null; } private void StopSliding() { if (_sliding == null) return; StopCoroutine(_sliding); _sliding = null; } private void Place(float y) { Banner.anchoredPosition = new Vector2(x, y); } #if UNITY_EDITOR // Les quatorze étapes préremplies dans l'ordre de Steps, pour n'avoir plus qu'à // écrire les messages. void Reset() { string[] pages = { "INSPIRATION", "DOORSTYLE", "COUNTERTOPSTYLE", "COLORROOM", "FLOORROOM", "ISLAND", "SYS", "PLAN2D", "SETDIM", "CEILING", "OBSTACLES", "POSITIONING", "WALLCAB", "SENDING", }; messages = new Entry[pages.Length]; for (int i = 0; i < pages.Length; i++) { messages[i] = new Entry { page = pages[i] }; } } // De quoi régler les deux positions sans lancer le Play. [ContextMenu("Aperçu : position au repos")] private void PreviewHidden() { Place(hiddenY); } [ContextMenu("Aperçu : position affichée")] private void PreviewShown() { Place(shownY); } #endif }