feat : add SubtitleSequencePlayer and SubtitleTriggerZone for managing subtitle playback

This commit is contained in:
Thibault Pouch
2026-03-10 14:41:42 +01:00
parent 6dd1c5efb8
commit b418333d67
8 changed files with 151 additions and 46 deletions

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: a315c9c77ad8a49238033181ece48806

View File

@@ -1,13 +1,13 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// Auto-spawns in Level01 and displays an intro subtitle sequence.
/// Reusable subtitle player that renders and plays subtitle lines loaded from JSON.
/// </summary>
public class Level01IntroSubtitles : MonoBehaviour
public class SubtitleSequencePlayer : MonoBehaviour
{
[System.Serializable]
[Serializable]
private struct SubtitleLine
{
public string speaker;
@@ -15,17 +15,15 @@ public class Level01IntroSubtitles : MonoBehaviour
public float duration;
}
[Header("Trigger")]
[SerializeField] private string targetSceneName = "Level01";
[SerializeField] private float initialDelay = 2.5f;
[Header("Subtitle Sequence")]
[SerializeField] private SubtitleLine[] lines =
[Serializable]
private struct SubtitleFile
{
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 },
};
public SubtitleLine[] lines;
}
[Header("Optional Default Data")]
[Tooltip("Used only if trigger zone calls PlayDefault().")]
[SerializeField] private TextAsset defaultSubtitleJson;
[SerializeField] private float typewriterCharsPerSecond = 40f;
[SerializeField] private float fadeDuration = 0.2f;
@@ -40,58 +38,55 @@ public class Level01IntroSubtitles : MonoBehaviour
[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 bool m_isPlaying;
private float m_alpha;
private SubtitleLine[] m_runtimeLines = Array.Empty<SubtitleLine>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetBootstrapFlag()
public bool IsPlaying => m_isPlaying;
public bool TryPlay(TextAsset subtitleJson, float initialDelay = 0f)
{
s_bootstrapped = false;
if (m_isPlaying)
return false;
if (!TryReadLinesFromJson(subtitleJson, out SubtitleLine[] parsedLines))
return false;
m_runtimeLines = parsedLines;
StartCoroutine(PlaySequence(initialDelay));
return true;
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
private static void Bootstrap()
public bool PlayDefault(float initialDelay = 0f)
{
if (s_bootstrapped)
return;
s_bootstrapped = true;
GameObject go = new GameObject(nameof(Level01IntroSubtitles));
go.hideFlags = HideFlags.DontSave;
go.AddComponent<Level01IntroSubtitles>();
return TryPlay(defaultSubtitleJson, initialDelay);
}
private void Start()
private IEnumerator PlaySequence(float initialDelay)
{
Scene activeScene = SceneManager.GetActiveScene();
if (activeScene.name != targetSceneName)
m_isPlaying = true;
if (m_runtimeLines == null || m_runtimeLines.Length == 0)
{
Destroy(gameObject);
return;
m_isPlaying = false;
yield break;
}
StartCoroutine(PlaySequence());
}
private IEnumerator PlaySequence()
{
if (initialDelay > 0f)
yield return new WaitForSeconds(initialDelay);
for (int i = 0; i < lines.Length; i++)
for (int i = 0; i < m_runtimeLines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i].text) || lines[i].duration <= 0f)
if (string.IsNullOrWhiteSpace(m_runtimeLines[i].text) || m_runtimeLines[i].duration <= 0f)
continue;
yield return StartCoroutine(ShowLine(lines[i]));
yield return StartCoroutine(ShowLine(m_runtimeLines[i]));
if (gapBetweenLines > 0f)
yield return new WaitForSeconds(gapBetweenLines);
@@ -99,7 +94,31 @@ public class Level01IntroSubtitles : MonoBehaviour
m_currentSpeaker = string.Empty;
m_currentText = string.Empty;
Destroy(gameObject);
m_isPlaying = false;
}
private bool TryReadLinesFromJson(TextAsset subtitleJson, out SubtitleLine[] parsedLines)
{
parsedLines = Array.Empty<SubtitleLine>();
if (subtitleJson == null || string.IsNullOrWhiteSpace(subtitleJson.text))
return false;
SubtitleFile file;
try
{
file = JsonUtility.FromJson<SubtitleFile>(subtitleJson.text);
}
catch
{
return false;
}
if (file.lines == null || file.lines.Length == 0)
return false;
parsedLines = file.lines;
return true;
}
private IEnumerator ShowLine(SubtitleLine line)
@@ -233,7 +252,6 @@ public class Level01IntroSubtitles : MonoBehaviour
clipping = TextClipping.Clip,
};
m_speakerStyle.normal.textColor = speakerColor;
}
private void OnDestroy()

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4947743d7bc9b4589b9932d429517d3a

View File

@@ -0,0 +1,51 @@
using UnityEngine;
/// <summary>
/// Trigger zone that starts a subtitle JSON sequence on a linked SubtitleSequencePlayer.
/// Put this on the zone collider object, and link the player on your empty object.
/// </summary>
[RequireComponent(typeof(Collider))]
public class SubtitleTriggerZone : MonoBehaviour
{
[Header("References")]
[SerializeField] private SubtitleSequencePlayer subtitlePlayer;
[SerializeField] private TextAsset subtitleJson;
[Header("Playback")]
[SerializeField] private float initialDelay = 0f;
[SerializeField] private bool oneShot = true;
private bool m_hasPlayed;
private void Reset()
{
Collider col = GetComponent<Collider>();
col.isTrigger = true;
}
private void OnTriggerEnter(Collider other)
{
if (!IsPlayer(other))
return;
if (oneShot && m_hasPlayed)
return;
if (subtitlePlayer == null)
return;
if (subtitlePlayer.TryPlay(subtitleJson, initialDelay))
m_hasPlayed = true;
}
private bool IsPlayer(Collider other)
{
if (other.CompareTag("Player"))
return true;
if (other.GetComponentInParent<PlayerMovement>() != null)
return true;
return false;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 33a33c65f75e2443383c2e29bd6bf5f1