using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; /// /// Auto-spawns in Level01 and displays an intro subtitle sequence. /// public class Level01IntroSubtitles : MonoBehaviour { [System.Serializable] private struct SubtitleLine { public string speaker; public string text; public float duration; } [Header("Trigger")] [SerializeField] private string targetSceneName = "Level01"; [SerializeField] private float initialDelay = 2.5f; [Header("Subtitle Sequence")] [SerializeField] private SubtitleLine[] lines = { new SubtitleLine { speaker = "SYSTEME", text = "...Ici, quelque chose cloche.", duration = 2.5f }, new SubtitleLine { speaker = "SYSTEME", text = "Reste calme. Observe la piece.", duration = 2.5f }, new SubtitleLine { speaker = "SYSTEME", text = "Trouve une sortie.", duration = 2.2f }, }; [SerializeField] private float typewriterCharsPerSecond = 40f; [SerializeField] private float fadeDuration = 0.2f; [SerializeField] private float gapBetweenLines = 0.15f; [Header("Visual")] [SerializeField] private int fontSize = 28; [SerializeField] private int speakerFontSize = 18; [SerializeField] private float horizontalPadding = 28f; [SerializeField] private float bottomOffset = 56f; [SerializeField] private Color textColor = new Color(1f, 1f, 1f, 1f); [SerializeField] private Color speakerColor = new Color(1f, 0.85f, 0.35f, 1f); [SerializeField] private Color backgroundColor = new Color(0f, 0f, 0f, 0.62f); private static bool s_bootstrapped; private string m_currentSpeaker; private string m_currentText; private GUIStyle m_textStyle; private GUIStyle m_speakerStyle; private Texture2D m_background; private bool m_isShowing; private float m_alpha; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetBootstrapFlag() { s_bootstrapped = false; } [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] private static void Bootstrap() { if (s_bootstrapped) return; s_bootstrapped = true; GameObject go = new GameObject(nameof(Level01IntroSubtitles)); go.hideFlags = HideFlags.DontSave; go.AddComponent(); } private void Start() { Scene activeScene = SceneManager.GetActiveScene(); if (activeScene.name != targetSceneName) { Destroy(gameObject); return; } StartCoroutine(PlaySequence()); } private IEnumerator PlaySequence() { if (initialDelay > 0f) yield return new WaitForSeconds(initialDelay); for (int i = 0; i < lines.Length; i++) { if (string.IsNullOrWhiteSpace(lines[i].text) || lines[i].duration <= 0f) continue; yield return StartCoroutine(ShowLine(lines[i])); if (gapBetweenLines > 0f) yield return new WaitForSeconds(gapBetweenLines); } m_currentSpeaker = string.Empty; m_currentText = string.Empty; Destroy(gameObject); } private IEnumerator ShowLine(SubtitleLine line) { m_currentSpeaker = line.speaker; m_currentText = string.Empty; m_isShowing = true; if (fadeDuration > 0f) { float fadeIn = 0f; while (fadeIn < fadeDuration) { fadeIn += Time.deltaTime; m_alpha = Mathf.Clamp01(fadeIn / fadeDuration); yield return null; } } else { m_alpha = 1f; } float typeTime = 0f; int totalChars = line.text.Length; if (typewriterCharsPerSecond > 0f) { while (m_currentText.Length < totalChars) { typeTime += Time.deltaTime; int visibleChars = Mathf.Clamp(Mathf.FloorToInt(typeTime * typewriterCharsPerSecond), 0, totalChars); m_currentText = line.text.Substring(0, visibleChars); yield return null; } } else { m_currentText = line.text; } float holdDuration = Mathf.Max(0f, line.duration - (typewriterCharsPerSecond > 0f ? typeTime : 0f)); if (holdDuration > 0f) yield return new WaitForSeconds(holdDuration); if (fadeDuration > 0f) { float fadeOut = fadeDuration; while (fadeOut > 0f) { fadeOut -= Time.deltaTime; m_alpha = Mathf.Clamp01(fadeOut / fadeDuration); yield return null; } } m_alpha = 0f; m_isShowing = false; } private void OnGUI() { if (!m_isShowing || string.IsNullOrEmpty(m_currentText)) return; EnsureStyles(); float maxWidth = Mathf.Min(Screen.width - 24f, 940f); float textWidth = maxWidth - horizontalPadding * 2f; float speakerHeight = string.IsNullOrEmpty(m_currentSpeaker) ? 0f : m_speakerStyle.CalcHeight(new GUIContent(m_currentSpeaker), textWidth); float textHeight = m_textStyle.CalcHeight(new GUIContent(string.IsNullOrEmpty(m_currentText) ? " " : m_currentText), textWidth); float boxWidth = maxWidth; float boxHeight = speakerHeight + textHeight + 28f; float boxX = (Screen.width - boxWidth) * 0.5f; float boxY = Screen.height - bottomOffset - boxHeight; Rect boxRect = new Rect(boxX, boxY, boxWidth, boxHeight); Color previousColor = GUI.color; GUI.color = new Color(1f, 1f, 1f, m_alpha); GUI.DrawTexture(boxRect, m_background); float yOffset = boxRect.y + 10f; if (!string.IsNullOrEmpty(m_currentSpeaker)) { Rect speakerRect = new Rect( boxRect.x + horizontalPadding, yOffset, textWidth, speakerHeight); GUI.Label(speakerRect, m_currentSpeaker, m_speakerStyle); yOffset += speakerHeight + 2f; } Rect textRect = new Rect( boxRect.x + horizontalPadding, yOffset, textWidth, textHeight); GUI.Label(textRect, m_currentText, m_textStyle); GUI.color = previousColor; } private void EnsureStyles() { if (m_textStyle != null) return; m_background = new Texture2D(1, 1); m_background.SetPixel(0, 0, backgroundColor); m_background.Apply(); m_textStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = fontSize, wordWrap = true, richText = false, clipping = TextClipping.Clip, }; m_textStyle.normal.textColor = textColor; m_speakerStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = speakerFontSize, fontStyle = FontStyle.Bold, wordWrap = false, clipping = TextClipping.Clip, }; m_speakerStyle.normal.textColor = speakerColor; } private void OnDestroy() { if (m_background != null) Destroy(m_background); } }