feat : add SubtitleSequencePlayer and SubtitleTriggerZone for managing subtitle playback
This commit is contained in:
@@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a315c9c77ad8a49238033181ece48806
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Auto-spawns in Level01 and displays an intro subtitle sequence.
|
/// Reusable subtitle player that renders and plays subtitle lines loaded from JSON.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Level01IntroSubtitles : MonoBehaviour
|
public class SubtitleSequencePlayer : MonoBehaviour
|
||||||
{
|
{
|
||||||
[System.Serializable]
|
[Serializable]
|
||||||
private struct SubtitleLine
|
private struct SubtitleLine
|
||||||
{
|
{
|
||||||
public string speaker;
|
public string speaker;
|
||||||
@@ -15,17 +15,15 @@ public class Level01IntroSubtitles : MonoBehaviour
|
|||||||
public float duration;
|
public float duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Header("Trigger")]
|
[Serializable]
|
||||||
[SerializeField] private string targetSceneName = "Level01";
|
private struct SubtitleFile
|
||||||
[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 },
|
public SubtitleLine[] lines;
|
||||||
new SubtitleLine { speaker = "SYSTEME", text = "Reste calme. Observe la piece.", duration = 2.5f },
|
}
|
||||||
new SubtitleLine { speaker = "SYSTEME", text = "Trouve une sortie.", duration = 2.2f },
|
|
||||||
};
|
[Header("Optional Default Data")]
|
||||||
|
[Tooltip("Used only if trigger zone calls PlayDefault().")]
|
||||||
|
[SerializeField] private TextAsset defaultSubtitleJson;
|
||||||
|
|
||||||
[SerializeField] private float typewriterCharsPerSecond = 40f;
|
[SerializeField] private float typewriterCharsPerSecond = 40f;
|
||||||
[SerializeField] private float fadeDuration = 0.2f;
|
[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 speakerColor = new Color(1f, 0.85f, 0.35f, 1f);
|
||||||
[SerializeField] private Color backgroundColor = new Color(0f, 0f, 0f, 0.62f);
|
[SerializeField] private Color backgroundColor = new Color(0f, 0f, 0f, 0.62f);
|
||||||
|
|
||||||
private static bool s_bootstrapped;
|
|
||||||
|
|
||||||
private string m_currentSpeaker;
|
private string m_currentSpeaker;
|
||||||
private string m_currentText;
|
private string m_currentText;
|
||||||
private GUIStyle m_textStyle;
|
private GUIStyle m_textStyle;
|
||||||
private GUIStyle m_speakerStyle;
|
private GUIStyle m_speakerStyle;
|
||||||
private Texture2D m_background;
|
private Texture2D m_background;
|
||||||
private bool m_isShowing;
|
private bool m_isShowing;
|
||||||
|
private bool m_isPlaying;
|
||||||
private float m_alpha;
|
private float m_alpha;
|
||||||
|
private SubtitleLine[] m_runtimeLines = Array.Empty<SubtitleLine>();
|
||||||
|
|
||||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
public bool IsPlaying => m_isPlaying;
|
||||||
private static void ResetBootstrapFlag()
|
|
||||||
|
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)]
|
public bool PlayDefault(float initialDelay = 0f)
|
||||||
private static void Bootstrap()
|
|
||||||
{
|
{
|
||||||
if (s_bootstrapped)
|
return TryPlay(defaultSubtitleJson, initialDelay);
|
||||||
return;
|
|
||||||
|
|
||||||
s_bootstrapped = true;
|
|
||||||
|
|
||||||
GameObject go = new GameObject(nameof(Level01IntroSubtitles));
|
|
||||||
go.hideFlags = HideFlags.DontSave;
|
|
||||||
go.AddComponent<Level01IntroSubtitles>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Start()
|
private IEnumerator PlaySequence(float initialDelay)
|
||||||
{
|
{
|
||||||
Scene activeScene = SceneManager.GetActiveScene();
|
m_isPlaying = true;
|
||||||
if (activeScene.name != targetSceneName)
|
|
||||||
|
if (m_runtimeLines == null || m_runtimeLines.Length == 0)
|
||||||
{
|
{
|
||||||
Destroy(gameObject);
|
m_isPlaying = false;
|
||||||
return;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
StartCoroutine(PlaySequence());
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerator PlaySequence()
|
|
||||||
{
|
|
||||||
if (initialDelay > 0f)
|
if (initialDelay > 0f)
|
||||||
yield return new WaitForSeconds(initialDelay);
|
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;
|
continue;
|
||||||
|
|
||||||
yield return StartCoroutine(ShowLine(lines[i]));
|
yield return StartCoroutine(ShowLine(m_runtimeLines[i]));
|
||||||
|
|
||||||
if (gapBetweenLines > 0f)
|
if (gapBetweenLines > 0f)
|
||||||
yield return new WaitForSeconds(gapBetweenLines);
|
yield return new WaitForSeconds(gapBetweenLines);
|
||||||
@@ -99,7 +94,31 @@ public class Level01IntroSubtitles : MonoBehaviour
|
|||||||
|
|
||||||
m_currentSpeaker = string.Empty;
|
m_currentSpeaker = string.Empty;
|
||||||
m_currentText = 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)
|
private IEnumerator ShowLine(SubtitleLine line)
|
||||||
@@ -233,7 +252,6 @@ public class Level01IntroSubtitles : MonoBehaviour
|
|||||||
clipping = TextClipping.Clip,
|
clipping = TextClipping.Clip,
|
||||||
};
|
};
|
||||||
m_speakerStyle.normal.textColor = speakerColor;
|
m_speakerStyle.normal.textColor = speakerColor;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
2
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs.meta
Normal file
2
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4947743d7bc9b4589b9932d429517d3a
|
||||||
51
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs
Normal file
51
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs.meta
Normal file
2
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 33a33c65f75e2443383c2e29bd6bf5f1
|
||||||
8
Assets/Code/Subtitles.meta
Normal file
8
Assets/Code/Subtitles.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3952fe191e7e945b3ba35d76408a51a6
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
19
Assets/Code/Subtitles/Level01IntroSubtitles.json
Normal file
19
Assets/Code/Subtitles/Level01IntroSubtitles.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"lines": [
|
||||||
|
{
|
||||||
|
"speaker": "SYSTEME",
|
||||||
|
"text": "...Ici, quelque chose cloche.",
|
||||||
|
"duration": 2.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "SYSTEME",
|
||||||
|
"text": "Reste calme. Observe la piece.",
|
||||||
|
"duration": 2.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "SYSTEME",
|
||||||
|
"text": "Trouve une sortie.",
|
||||||
|
"duration": 2.2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
Assets/Code/Subtitles/Level01IntroSubtitles.json.meta
Normal file
7
Assets/Code/Subtitles/Level01IntroSubtitles.json.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c4b9d13b29337441dbdb06a8a45e32c3
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user