Loading Assets/Scripts/Messaging/TextTyper.cs 0 → 100644 +118 −0 Original line number Diff line number Diff line using System; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; namespace Messaging { public class TextTyper { private bool _textAnimating; private bool _stopAnimating; private readonly TMP_Text _textBox; public TextTyper(TMP_Text textBox) { _textBox = textBox; } public IEnumerator AnimateTextIn(List<DialogueCommand> commands, string processedMessage, Action onFinish) { _textAnimating = true; float secondsPerCharacter = 1f / 150f; float timeOfLastCharacter = 0; TMP_TextInfo textInfo = _textBox.textInfo; foreach (TMP_MeshInfo meshInfer in textInfo.meshInfo) { if (meshInfer.vertices == null) continue; for (int j = 0; j < meshInfer.vertices.Length; j++) { meshInfer.vertices[j] = Vector3.zero; } } _textBox.text = processedMessage; _textBox.ForceMeshUpdate(); Color32[][] originalColors = new Color32[textInfo.meshInfo.Length][]; for (int i = 0; i < originalColors.Length; i++) { Color32[] theColors = textInfo.meshInfo[i].colors32; originalColors[i] = new Color32[theColors.Length]; Array.Copy(theColors, originalColors[i], theColors.Length); } int charCount = textInfo.characterCount; float[] charAnimStartTimes = new float[charCount]; for (int i = 0; i < charCount; i++) { charAnimStartTimes[i] = -1; } int visibleCharacterIndex = 0; while (true) { if (_stopAnimating) { for (int i = visibleCharacterIndex; i < charCount; i++) { charAnimStartTimes[i] = Time.unscaledTime; } visibleCharacterIndex = charCount; FinishAnimating(onFinish); } if (ShouldShowNextCharacter(secondsPerCharacter, timeOfLastCharacter)) { if (visibleCharacterIndex <= charCount) { ExecuteCommandsForCurrentIndex(commands, visibleCharacterIndex, ref secondsPerCharacter, ref timeOfLastCharacter); if (visibleCharacterIndex < charCount && ShouldShowNextCharacter(secondsPerCharacter, timeOfLastCharacter)) { charAnimStartTimes[visibleCharacterIndex] = Time.unscaledTime; visibleCharacterIndex++; timeOfLastCharacter = Time.unscaledTime; if (visibleCharacterIndex == charCount) { FinishAnimating(onFinish); } } } } _textBox.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32); for (int i = 0; i < textInfo.meshInfo.Length; i++) { TMP_MeshInfo theInfo = textInfo.meshInfo[i]; theInfo.mesh.vertices = theInfo.vertices; _textBox.UpdateGeometry(theInfo.mesh, i); } yield return null; } } private static void ExecuteCommandsForCurrentIndex(List<DialogueCommand> commands, int visableCharacterIndex, ref float secondsPerCharacter, ref float timeOfLastCharacter) { for (int i = 0; i < commands.Count; i++) { DialogueCommand command = commands[i]; if (command.Position != visableCharacterIndex) continue; switch (command.Type) { case DialogueCommandType.Pause: timeOfLastCharacter = Time.unscaledTime + command.FloatValue; break; case DialogueCommandType.TextSpeedChange: secondsPerCharacter = 1f / command.FloatValue; break; } commands.RemoveAt(i); i--; } } private void FinishAnimating(Action onFinish) { _textAnimating = false; _stopAnimating = false; onFinish?.Invoke(); } private static bool ShouldShowNextCharacter(float secondsPerCharacter, float timeOfLastCharacter) { return (Time.unscaledTime - timeOfLastCharacter) > secondsPerCharacter; } public void SkipToEndOfCurrentMessage() { if (_textAnimating) { _stopAnimating = true; } } public bool IsMessageAnimating() { return _textAnimating; } } } No newline at end of file Assets/Scripts/Messaging/TextTyper.cs.meta 0 → 100644 +11 −0 Original line number Diff line number Diff line fileFormatVersion: 2 guid: dd35e3193d336f24785ac649187905f8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Assets/Scripts/Messaging/TextUtility.cs 0 → 100644 +94 −0 Original line number Diff line number Diff line using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; namespace Messaging { public class DialogueUtility : MonoBehaviour { private const string REMAINDER_REGEX = "(.*?((?=>)|(/|$)))"; private const string PAUSE_REGEX_STRING = "<p:(?<pause>" + REMAINDER_REGEX + ")>"; private static readonly Regex PauseRegex = new(PAUSE_REGEX_STRING); private const string SPEED_REGEX_STRING = "<sp:(?<speed>" + REMAINDER_REGEX + ")>"; private static readonly Regex SpeedRegex = new(SPEED_REGEX_STRING); private static readonly Dictionary<string, float> PauseDictionary = new() { { "tiny", .1f }, { "short", .25f }, { "normal", 0.666f }, { "long", 1f }, { "read", 2f }, }; public static List<DialogueCommand> ProcessInputString(string message, out string processedMessage) { List<DialogueCommand> result = new(); processedMessage = message; processedMessage = HandlePauseTags(processedMessage, result); processedMessage = HandleSpeedTags(processedMessage, result); return result; } private static string HandleSpeedTags(string processedMessage, List<DialogueCommand> result) { MatchCollection speedMatches = SpeedRegex.Matches(processedMessage); foreach (Match match in speedMatches) { string stringVal = match.Groups["speed"].Value; if (!float.TryParse(stringVal, out float val)) { val = 150f; } result.Add(new DialogueCommand { Position = VisibleCharactersUpToIndex(processedMessage, match.Index), Type = DialogueCommandType.TextSpeedChange, FloatValue = val }); } processedMessage = Regex.Replace(processedMessage, SPEED_REGEX_STRING, ""); return processedMessage; } private static string HandlePauseTags(string processedMessage, List<DialogueCommand> result) { MatchCollection pauseMatches = PauseRegex.Matches(processedMessage); foreach (Match match in pauseMatches) { string val = match.Groups["pause"].Value; string pauseName = val; Debug.Assert(PauseDictionary.ContainsKey(pauseName), "no pause registered for '" + pauseName + "'"); result.Add(new DialogueCommand { Position = VisibleCharactersUpToIndex(processedMessage, match.Index), Type = DialogueCommandType.Pause, FloatValue = PauseDictionary[pauseName] }); } processedMessage = Regex.Replace(processedMessage, PAUSE_REGEX_STRING, ""); return processedMessage; } private static int VisibleCharactersUpToIndex(string message, int index) { int result = 0; bool insideBrackets = false; for (int i = 0; i < index; i++) { if (message[i] == '<') { insideBrackets = true; } else if (message[i] == '>') { insideBrackets = false; result--; } if (!insideBrackets) { result++; } else if (i + 6 < index && message.Substring(i, 6) == "sprite") { result++; } } return result; } } public struct DialogueCommand { public int Position; public DialogueCommandType Type; public float FloatValue; } public enum DialogueCommandType { Pause, TextSpeedChange, } } No newline at end of file Assets/Scripts/Messaging/TextUtility.cs.meta 0 → 100644 +11 −0 Original line number Diff line number Diff line fileFormatVersion: 2 guid: 7cb18af2a0fb6af4abf80654e793de64 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: Loading
Assets/Scripts/Messaging/TextTyper.cs 0 → 100644 +118 −0 Original line number Diff line number Diff line using System; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; namespace Messaging { public class TextTyper { private bool _textAnimating; private bool _stopAnimating; private readonly TMP_Text _textBox; public TextTyper(TMP_Text textBox) { _textBox = textBox; } public IEnumerator AnimateTextIn(List<DialogueCommand> commands, string processedMessage, Action onFinish) { _textAnimating = true; float secondsPerCharacter = 1f / 150f; float timeOfLastCharacter = 0; TMP_TextInfo textInfo = _textBox.textInfo; foreach (TMP_MeshInfo meshInfer in textInfo.meshInfo) { if (meshInfer.vertices == null) continue; for (int j = 0; j < meshInfer.vertices.Length; j++) { meshInfer.vertices[j] = Vector3.zero; } } _textBox.text = processedMessage; _textBox.ForceMeshUpdate(); Color32[][] originalColors = new Color32[textInfo.meshInfo.Length][]; for (int i = 0; i < originalColors.Length; i++) { Color32[] theColors = textInfo.meshInfo[i].colors32; originalColors[i] = new Color32[theColors.Length]; Array.Copy(theColors, originalColors[i], theColors.Length); } int charCount = textInfo.characterCount; float[] charAnimStartTimes = new float[charCount]; for (int i = 0; i < charCount; i++) { charAnimStartTimes[i] = -1; } int visibleCharacterIndex = 0; while (true) { if (_stopAnimating) { for (int i = visibleCharacterIndex; i < charCount; i++) { charAnimStartTimes[i] = Time.unscaledTime; } visibleCharacterIndex = charCount; FinishAnimating(onFinish); } if (ShouldShowNextCharacter(secondsPerCharacter, timeOfLastCharacter)) { if (visibleCharacterIndex <= charCount) { ExecuteCommandsForCurrentIndex(commands, visibleCharacterIndex, ref secondsPerCharacter, ref timeOfLastCharacter); if (visibleCharacterIndex < charCount && ShouldShowNextCharacter(secondsPerCharacter, timeOfLastCharacter)) { charAnimStartTimes[visibleCharacterIndex] = Time.unscaledTime; visibleCharacterIndex++; timeOfLastCharacter = Time.unscaledTime; if (visibleCharacterIndex == charCount) { FinishAnimating(onFinish); } } } } _textBox.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32); for (int i = 0; i < textInfo.meshInfo.Length; i++) { TMP_MeshInfo theInfo = textInfo.meshInfo[i]; theInfo.mesh.vertices = theInfo.vertices; _textBox.UpdateGeometry(theInfo.mesh, i); } yield return null; } } private static void ExecuteCommandsForCurrentIndex(List<DialogueCommand> commands, int visableCharacterIndex, ref float secondsPerCharacter, ref float timeOfLastCharacter) { for (int i = 0; i < commands.Count; i++) { DialogueCommand command = commands[i]; if (command.Position != visableCharacterIndex) continue; switch (command.Type) { case DialogueCommandType.Pause: timeOfLastCharacter = Time.unscaledTime + command.FloatValue; break; case DialogueCommandType.TextSpeedChange: secondsPerCharacter = 1f / command.FloatValue; break; } commands.RemoveAt(i); i--; } } private void FinishAnimating(Action onFinish) { _textAnimating = false; _stopAnimating = false; onFinish?.Invoke(); } private static bool ShouldShowNextCharacter(float secondsPerCharacter, float timeOfLastCharacter) { return (Time.unscaledTime - timeOfLastCharacter) > secondsPerCharacter; } public void SkipToEndOfCurrentMessage() { if (_textAnimating) { _stopAnimating = true; } } public bool IsMessageAnimating() { return _textAnimating; } } } No newline at end of file
Assets/Scripts/Messaging/TextTyper.cs.meta 0 → 100644 +11 −0 Original line number Diff line number Diff line fileFormatVersion: 2 guid: dd35e3193d336f24785ac649187905f8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant:
Assets/Scripts/Messaging/TextUtility.cs 0 → 100644 +94 −0 Original line number Diff line number Diff line using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; namespace Messaging { public class DialogueUtility : MonoBehaviour { private const string REMAINDER_REGEX = "(.*?((?=>)|(/|$)))"; private const string PAUSE_REGEX_STRING = "<p:(?<pause>" + REMAINDER_REGEX + ")>"; private static readonly Regex PauseRegex = new(PAUSE_REGEX_STRING); private const string SPEED_REGEX_STRING = "<sp:(?<speed>" + REMAINDER_REGEX + ")>"; private static readonly Regex SpeedRegex = new(SPEED_REGEX_STRING); private static readonly Dictionary<string, float> PauseDictionary = new() { { "tiny", .1f }, { "short", .25f }, { "normal", 0.666f }, { "long", 1f }, { "read", 2f }, }; public static List<DialogueCommand> ProcessInputString(string message, out string processedMessage) { List<DialogueCommand> result = new(); processedMessage = message; processedMessage = HandlePauseTags(processedMessage, result); processedMessage = HandleSpeedTags(processedMessage, result); return result; } private static string HandleSpeedTags(string processedMessage, List<DialogueCommand> result) { MatchCollection speedMatches = SpeedRegex.Matches(processedMessage); foreach (Match match in speedMatches) { string stringVal = match.Groups["speed"].Value; if (!float.TryParse(stringVal, out float val)) { val = 150f; } result.Add(new DialogueCommand { Position = VisibleCharactersUpToIndex(processedMessage, match.Index), Type = DialogueCommandType.TextSpeedChange, FloatValue = val }); } processedMessage = Regex.Replace(processedMessage, SPEED_REGEX_STRING, ""); return processedMessage; } private static string HandlePauseTags(string processedMessage, List<DialogueCommand> result) { MatchCollection pauseMatches = PauseRegex.Matches(processedMessage); foreach (Match match in pauseMatches) { string val = match.Groups["pause"].Value; string pauseName = val; Debug.Assert(PauseDictionary.ContainsKey(pauseName), "no pause registered for '" + pauseName + "'"); result.Add(new DialogueCommand { Position = VisibleCharactersUpToIndex(processedMessage, match.Index), Type = DialogueCommandType.Pause, FloatValue = PauseDictionary[pauseName] }); } processedMessage = Regex.Replace(processedMessage, PAUSE_REGEX_STRING, ""); return processedMessage; } private static int VisibleCharactersUpToIndex(string message, int index) { int result = 0; bool insideBrackets = false; for (int i = 0; i < index; i++) { if (message[i] == '<') { insideBrackets = true; } else if (message[i] == '>') { insideBrackets = false; result--; } if (!insideBrackets) { result++; } else if (i + 6 < index && message.Substring(i, 6) == "sprite") { result++; } } return result; } } public struct DialogueCommand { public int Position; public DialogueCommandType Type; public float FloatValue; } public enum DialogueCommandType { Pause, TextSpeedChange, } } No newline at end of file
Assets/Scripts/Messaging/TextUtility.cs.meta 0 → 100644 +11 −0 Original line number Diff line number Diff line fileFormatVersion: 2 guid: 7cb18af2a0fb6af4abf80654e793de64 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: