diff --git a/Assets/Code/Scripts/Level.meta b/Assets/Code/Scripts/Level.meta new file mode 100644 index 0000000..9210c49 --- /dev/null +++ b/Assets/Code/Scripts/Level.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e7b9c54377674993a7922f84e9cfcce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs b/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs new file mode 100644 index 0000000..c2878f5 --- /dev/null +++ b/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs @@ -0,0 +1,244 @@ +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); + } +} diff --git a/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs.meta b/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs.meta new file mode 100644 index 0000000..727a995 --- /dev/null +++ b/Assets/Code/Scripts/Level/Level01IntroSubtitles.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a315c9c77ad8a49238033181ece48806 \ No newline at end of file