From 3cba8b0838143d5e32e0c43951df8900021a53b4 Mon Sep 17 00:00:00 2001 From: Edward Rowe Date: Thu, 9 Mar 2017 10:14:11 -0500 Subject: [PATCH] Cleanup state in TextTyper printing (issue #2) This adds a TypedTextGenerator that provides a pure function to return the print string at a desired place in a line of dialogue. This resolves some of the issues I had with mishandled state, namely issue #7 and issue #5. --- Assets/RedBlueGames/TextTyper/RichTextTag.cs | 2 + .../Tests/Editor/RichTextTagTests.cs | 110 ++-- .../Tests/Editor/TypedTextGeneratorTests.cs | 318 +++++++++++ .../Editor/TypedTextGeneratorTests.cs.meta | 12 + ...I_Example.unity => TextTyperExample.unity} | 529 ++++++++++++++++-- ...unity.meta => TextTyperExample.unity.meta} | 0 .../{Test_UGUI.cs => TextTyperTester.cs} | 68 ++- ...t_UGUI.cs.meta => TextTyperTester.cs.meta} | 0 Assets/RedBlueGames/TextTyper/TextTyper.cs | 171 +----- .../TextTyper/TypedTextGenerator.cs | 347 ++++++++++++ .../TextTyper/TypedTextGenerator.cs.meta | 12 + 11 files changed, 1284 insertions(+), 285 deletions(-) create mode 100644 Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs create mode 100644 Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs.meta rename Assets/RedBlueGames/TextTyper/Tests/{UGUI_Example.unity => TextTyperExample.unity} (60%) rename Assets/RedBlueGames/TextTyper/Tests/{UGUI_Example.unity.meta => TextTyperExample.unity.meta} (100%) rename Assets/RedBlueGames/TextTyper/Tests/{Test_UGUI.cs => TextTyperTester.cs} (64%) rename Assets/RedBlueGames/TextTyper/Tests/{Test_UGUI.cs.meta => TextTyperTester.cs.meta} (100%) create mode 100644 Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs create mode 100644 Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs.meta diff --git a/Assets/RedBlueGames/TextTyper/RichTextTag.cs b/Assets/RedBlueGames/TextTyper/RichTextTag.cs index f36b271..74c8a1b 100644 --- a/Assets/RedBlueGames/TextTyper/RichTextTag.cs +++ b/Assets/RedBlueGames/TextTyper/RichTextTag.cs @@ -8,6 +8,8 @@ /// public class RichTextTag { + public static readonly RichTextTag ClearColorTag = new RichTextTag(""); + private const char OpeningNodeDelimeter = '<'; private const char CloseNodeDelimeter = '>'; private const char EndTagDelimeter = '/'; diff --git a/Assets/RedBlueGames/TextTyper/Tests/Editor/RichTextTagTests.cs b/Assets/RedBlueGames/TextTyper/Tests/Editor/RichTextTagTests.cs index ba23291..3f12860 100644 --- a/Assets/RedBlueGames/TextTyper/Tests/Editor/RichTextTagTests.cs +++ b/Assets/RedBlueGames/TextTyper/Tests/Editor/RichTextTagTests.cs @@ -1,58 +1,60 @@ -using UnityEngine; -using UnityEditor; -using NUnit.Framework; -using RedBlueGames.Tools.TextTyper; - -public class RichTextTagTests +namespace RedBlueGames.Tools.TextTyper.Tests { - [Test] - public void Constructor_OpeningTag_Parses() - { - //Arrange - var tag = ""; - - //Act - var richTextTag = new RichTextTag(tag); - - //Assert - Assert.AreEqual(tag, richTextTag.TagText); - Assert.AreEqual("b", richTextTag.TagType); - Assert.IsFalse(richTextTag.IsClosingTag); - Assert.AreEqual("", richTextTag.ClosingTagText); - Assert.AreEqual(string.Empty, richTextTag.Parameter); - } - - [Test] - public void Constructor_TagAndParameter_Parses() - { - //Arrange - var tag = ""; - - //Act - var richTextTag = new RichTextTag(tag); - - //Assert - Assert.AreEqual(tag, richTextTag.TagText); - Assert.AreEqual("color", richTextTag.TagType); - Assert.IsFalse(richTextTag.IsClosingTag); - Assert.AreEqual("", richTextTag.ClosingTagText); - Assert.AreEqual("#FFFFFFFF", richTextTag.Parameter); - } + using UnityEngine; + using UnityEditor; + using NUnit.Framework; - [Test] - public void Constructor_ClosingTag_Parses() + public class RichTextTagTests { - //Arrange - var tag = ""; - - //Act - var richTextTag = new RichTextTag(tag); - - //Assert - Assert.AreEqual(tag, richTextTag.TagText); - Assert.AreEqual("color", richTextTag.TagType); - Assert.IsTrue(richTextTag.IsClosingTag); - Assert.AreEqual("", richTextTag.ClosingTagText); - Assert.AreEqual(string.Empty, richTextTag.Parameter); + [Test] + public void Constructor_OpeningTag_Parses() + { + //Arrange + var tag = ""; + + //Act + var richTextTag = new RichTextTag(tag); + + //Assert + Assert.AreEqual(tag, richTextTag.TagText); + Assert.AreEqual("b", richTextTag.TagType); + Assert.IsFalse(richTextTag.IsClosingTag); + Assert.AreEqual("", richTextTag.ClosingTagText); + Assert.AreEqual(string.Empty, richTextTag.Parameter); + } + + [Test] + public void Constructor_TagAndParameter_Parses() + { + //Arrange + var tag = ""; + + //Act + var richTextTag = new RichTextTag(tag); + + //Assert + Assert.AreEqual(tag, richTextTag.TagText); + Assert.AreEqual("color", richTextTag.TagType); + Assert.IsFalse(richTextTag.IsClosingTag); + Assert.AreEqual("", richTextTag.ClosingTagText); + Assert.AreEqual("#FFFFFFFF", richTextTag.Parameter); + } + + [Test] + public void Constructor_ClosingTag_Parses() + { + //Arrange + var tag = ""; + + //Act + var richTextTag = new RichTextTag(tag); + + //Assert + Assert.AreEqual(tag, richTextTag.TagText); + Assert.AreEqual("color", richTextTag.TagType); + Assert.IsTrue(richTextTag.IsClosingTag); + Assert.AreEqual("", richTextTag.ClosingTagText); + Assert.AreEqual(string.Empty, richTextTag.Parameter); + } } -} +} \ No newline at end of file diff --git a/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs b/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs new file mode 100644 index 0000000..903b4b1 --- /dev/null +++ b/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs @@ -0,0 +1,318 @@ +namespace RedBlueGames.Tools.TextTyper.Tests +{ + using UnityEditor; + using UnityEngine; + using NUnit.Framework; + + public class TypedTextGeneratorTests + { + [Test] + public void GetText_EmptyString_ReturnsEmptyAndCompleted() + { + var textToType = string.Empty; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = textToType; + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + Assert.AreEqual(char.MinValue, generatedText.LastPrintedChar); + Assert.AreEqual(true, generatedText.IsComplete); + Assert.AreEqual(0.0, generatedText.Delay); + } + + [Test] + public void GetText_OnlyUnityRichTextTags_ReturnsEmptyAndCompleted() + { + var textToType = ""; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 1); + + var expectedText = textToType; + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + Assert.AreEqual(char.MinValue, generatedText.LastPrintedChar); + Assert.AreEqual(true, generatedText.IsComplete); + Assert.AreEqual(0.0, generatedText.Delay); + } + + [Test] + public void GetText_OnlyCustomRichTextTags_ReturnsEmptyAndCompleted() + { + var textToType = ""; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 1); + + var expectedText = string.Empty; + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + Assert.AreEqual(char.MinValue, generatedText.LastPrintedChar); + Assert.AreEqual(true, generatedText.IsComplete); + Assert.AreEqual(0.0, generatedText.Delay); + } + + [Test] + public void GetText_VisibleCharIndexOutOfBounds_ReturnsFullString() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 9999); + + var expectedText = textToType; + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_NoTagsFirstChar_ShowsFirstChar() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 0); + + var expectedText = string.Concat("H", RichTextTag.ClearColorTag, "ello world", RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_NoTagsLastChar_ReturnsFullString() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, textToType.Length - 1); + + var expectedText = textToType; + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_NoTagsNextToLastChar_ShowsAllButLast() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, textToType.Length - 2); + + var expectedText = string.Concat("Hello worl", RichTextTag.ClearColorTag, "d", RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_BeforeTagsAtEnd_CorrectlyHidesTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 1); + + var expectedText = string.Concat("He", RichTextTag.ClearColorTag, "llo world", RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_MiddleOfTag_HidesTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_AfterTags_ShowsTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_AllUnityTags_PrintsCorrectly() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_BeforeColorTags_HidesAllTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 3); + + var expectedText = string.Concat( + "Hell", + RichTextTag.ClearColorTag, + "o world", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_AfterColorTags_ShowsAllTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_MiddleOfColorTags_PrintsCorrectly() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_ExactlyOnColorTagEnd_PrintsCorrectly() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 4); + + var expectedText = string.Concat( + "Hello", + RichTextTag.ClearColorTag, + " world", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_ExactlyOnColorTagStart_PrintsCorrectly() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 6); + + var expectedText = string.Concat( + "Hello w", + RichTextTag.ClearColorTag, + "orld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void GetText_IncludeCustomTags_RemovesCustomTags() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + var expectedText = string.Concat( + "Hello wor", + RichTextTag.ClearColorTag, + "ld", + RichTextTag.ClearColorTag.ClosingTagText); + + Assert.AreEqual(expectedText, generatedText.TextToPrint); + } + + [Test] + public void Delay_DelayTagIsActive_ReturnsCorrectDelay() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 4); + + Assert.AreEqual(0.5f, generatedText.Delay); + } + + [Test] + public void Delay_DelayTagIsNotActive_ReturnsNoDelay() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 8); + + Assert.AreEqual(0.0, generatedText.Delay); + } + + [Test] + public void LastPrintedCharacter_FirstLetter_ReturnsFirst() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 0); + + Assert.AreEqual('H', generatedText.LastPrintedChar); + } + + [Test] + public void LastPrintedCharacter_SecondLetter_ReturnsSecond() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 1); + + Assert.AreEqual('e', generatedText.LastPrintedChar); + } + + [Test] + public void LastPrintedCharacter_JustAfterTag_ReturnsChar() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 5); + + Assert.AreEqual(' ', generatedText.LastPrintedChar); + } + + [Test] + public void LastPrintedCharacter_LastCharacterIsTag_ReturnsNonTagChar() + { + var textToType = "Hello world"; + var generator = new TypedTextGenerator(); + var generatedText = generator.GetTypedTextAt(textToType, 11); + + Assert.AreEqual('d', generatedText.LastPrintedChar); + } + } +} \ No newline at end of file diff --git a/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs.meta b/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs.meta new file mode 100644 index 0000000..de90d3f --- /dev/null +++ b/Assets/RedBlueGames/TextTyper/Tests/Editor/TypedTextGeneratorTests.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fa7143360c0f04e0ba96b18c8b979562 +timeCreated: 1488844575 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/RedBlueGames/TextTyper/Tests/UGUI_Example.unity b/Assets/RedBlueGames/TextTyper/Tests/TextTyperExample.unity similarity index 60% rename from Assets/RedBlueGames/TextTyper/Tests/UGUI_Example.unity rename to Assets/RedBlueGames/TextTyper/Tests/TextTyperExample.unity index f9101b3..272ffdc 100644 --- a/Assets/RedBlueGames/TextTyper/Tests/UGUI_Example.unity +++ b/Assets/RedBlueGames/TextTyper/Tests/TextTyperExample.unity @@ -85,6 +85,116 @@ NavMeshSettings: cellSize: 0.16666666 manualCellSize: 0 m_NavMeshData: {fileID: 0} +--- !u!1 &576120086 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 224: {fileID: 576120087} + - 222: {fileID: 576120090} + - 114: {fileID: 576120089} + - 114: {fileID: 576120088} + m_Layer: 5 + m_Name: Button (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &576120087 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 576120086} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 962887286} + m_Father: {fileID: 1129080913} + m_RootOrder: 1 + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &576120088 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 576120086} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 576120089} + m_OnClick: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &576120089 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 576120086} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &576120090 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 576120086} --- !u!1 &655492769 GameObject: m_ObjectHideFlags: 0 @@ -96,7 +206,6 @@ GameObject: - 223: {fileID: 655492772} - 114: {fileID: 655492771} - 114: {fileID: 655492770} - - 114: {fileID: 655492774} m_Layer: 5 m_Name: Canvas m_TagString: Untagged @@ -171,6 +280,7 @@ RectTransform: m_Children: - {fileID: 1107687252} - {fileID: 1012501196} + - {fileID: 1129080913} m_Father: {fileID: 0} m_RootOrder: 1 m_AnchorMin: {x: 0, y: 0} @@ -178,20 +288,189 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} ---- !u!114 &655492774 +--- !u!1 &961921199 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 224: {fileID: 961921200} + - 222: {fileID: 961921203} + - 114: {fileID: 961921202} + - 114: {fileID: 961921201} + m_Layer: 5 + m_Name: Button + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &961921200 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 961921199} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1368949211} + m_Father: {fileID: 1129080913} + m_RootOrder: 0 + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &961921201 MonoBehaviour: m_ObjectHideFlags: 0 m_PrefabParentObject: {fileID: 0} m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 655492769} + m_GameObject: {fileID: 961921199} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ece93e789392cab498408406d4438896, type: 3} + m_Script: {fileID: 1392445389, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} m_Name: m_EditorClassIdentifier: - printSoundEffect: {fileID: 8300000, guid: 4457c27178d8e4ec2aef0a0406b75e06, type: 3} - text: {fileID: 1158326231} - testTextTyper: {fileID: 1158326234} + m_Navigation: + m_Mode: 3 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 961921202} + m_OnClick: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.Button+ButtonClickedEvent, UnityEngine.UI, Version=1.0.0.0, + Culture=neutral, PublicKeyToken=null +--- !u!114 &961921202 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 961921199} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -765806418, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 +--- !u!222 &961921203 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 961921199} +--- !u!1 &962887285 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 224: {fileID: 962887286} + - 222: {fileID: 962887288} + - 114: {fileID: 962887287} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &962887286 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 962887285} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 576120087} + m_RootOrder: 0 + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &962887287 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 962887285} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Advance (no skip) +--- !u!222 &962887288 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 962887285} --- !u!1 &1012501195 GameObject: m_ObjectHideFlags: 0 @@ -202,7 +481,6 @@ GameObject: - 224: {fileID: 1012501196} - 222: {fileID: 1012501198} - 114: {fileID: 1012501197} - - 114: {fileID: 1012501199} - 114: {fileID: 1012501200} m_Layer: 5 m_Name: Window @@ -227,8 +505,8 @@ RectTransform: m_RootOrder: 1 m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -474, y: -195} - m_SizeDelta: {x: 251, y: 256} + m_AnchoredPosition: {x: -1, y: -224} + m_SizeDelta: {x: 1196, y: 199} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1012501197 MonoBehaviour: @@ -263,53 +541,6 @@ CanvasRenderer: m_PrefabParentObject: {fileID: 0} m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 1012501195} ---- !u!114 &1012501199 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_GameObject: {fileID: 1012501195} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: -1862395651, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Delegates: - - eventID: 2 - callback: - m_PersistentCalls: - m_Calls: - - m_Target: {fileID: 655492774} - m_MethodName: OnClickWindow - m_Mode: 1 - m_Arguments: - m_ObjectArgument: {fileID: 0} - m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine - m_IntArgument: 0 - m_FloatArgument: 0 - m_StringArgument: - m_BoolArgument: 0 - m_CallState: 2 - m_TypeName: UnityEngine.EventSystems.EventTrigger+TriggerEvent, UnityEngine.UI, - Version=1.0.0.0, Culture=neutral, PublicKeyToken=null - delegates: - - eventID: 2 - callback: - m_PersistentCalls: - m_Calls: - - m_Target: {fileID: 655492774} - m_MethodName: OnClickWindow - m_Mode: 1 - m_Arguments: - m_ObjectArgument: {fileID: 0} - m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine - m_IntArgument: 0 - m_FloatArgument: 0 - m_StringArgument: - m_BoolArgument: 0 - m_CallState: 2 - m_TypeName: UnityEngine.EventSystems.EventTrigger+TriggerEvent, UnityEngine.UI, - Version=1.0.0.0, Culture=neutral, PublicKeyToken=null --- !u!114 &1012501200 MonoBehaviour: m_ObjectHideFlags: 0 @@ -397,6 +628,71 @@ CanvasRenderer: m_PrefabParentObject: {fileID: 0} m_PrefabInternal: {fileID: 0} m_GameObject: {fileID: 1107687251} +--- !u!1 &1129080912 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 224: {fileID: 1129080913} + - 222: {fileID: 1129080915} + - 114: {fileID: 1129080914} + m_Layer: 5 + m_Name: TestButtons + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1129080913 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1129080912} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 961921200} + - {fileID: 576120087} + m_Father: {fileID: 655492773} + m_RootOrder: 2 + m_AnchorMin: {x: 0.5, y: 1} + m_AnchorMax: {x: 0.5, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 1280, y: 144} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &1129080914 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1129080912} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -2095666955, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 5 + m_Right: 0 + m_Top: 5 + m_Bottom: 0 + m_ChildAlignment: 0 + m_StartCorner: 0 + m_StartAxis: 0 + m_CellSize: {x: 200, y: 50} + m_Spacing: {x: 0, y: 0} + m_Constraint: 0 + m_ConstraintCount: 2 +--- !u!222 &1129080915 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1129080912} --- !u!1 &1130243728 GameObject: m_ObjectHideFlags: 0 @@ -612,6 +908,79 @@ MonoBehaviour: m_Calls: [] m_TypeName: RedBlueGames.Tools.TextTyper.TextTyper+CharacterPrintedEvent, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null +--- !u!1 &1368949210 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 224: {fileID: 1368949211} + - 222: {fileID: 1368949213} + - 114: {fileID: 1368949212} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1368949211 +RectTransform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1368949210} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 961921200} + m_RootOrder: 0 + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1368949212 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1368949210} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 708705254, guid: f5f67c52d1564df4a8936ccd202a3bd8, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI, + Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Skip or Advance +--- !u!222 &1368949213 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1368949210} --- !u!1 &1532273923 GameObject: m_ObjectHideFlags: 0 @@ -686,6 +1055,50 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_RootOrder: 2 +--- !u!1 &1888324654 +GameObject: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 4 + m_Component: + - 4: {fileID: 1888324656} + - 114: {fileID: 1888324655} + m_Layer: 0 + m_Name: TextTyperTester + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1888324655 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1888324654} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ece93e789392cab498408406d4438896, type: 3} + m_Name: + m_EditorClassIdentifier: + printSoundEffect: {fileID: 0} + text: {fileID: 1158326231} + printNextButton: {fileID: 961921201} + printNoSkipButton: {fileID: 576120088} + testTextTyper: {fileID: 1158326234} +--- !u!4 &1888324656 +Transform: + m_ObjectHideFlags: 0 + m_PrefabParentObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 1888324654} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 681, y: 383, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 --- !u!1 &1972748366 GameObject: m_ObjectHideFlags: 0 @@ -754,7 +1167,7 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 1 m_LineSpacing: 1 - m_Text: Generic NPC + m_Text: Sir Talksalot --- !u!222 &1972748369 CanvasRenderer: m_ObjectHideFlags: 0 diff --git a/Assets/RedBlueGames/TextTyper/Tests/UGUI_Example.unity.meta b/Assets/RedBlueGames/TextTyper/Tests/TextTyperExample.unity.meta similarity index 100% rename from Assets/RedBlueGames/TextTyper/Tests/UGUI_Example.unity.meta rename to Assets/RedBlueGames/TextTyper/Tests/TextTyperExample.unity.meta diff --git a/Assets/RedBlueGames/TextTyper/Tests/Test_UGUI.cs b/Assets/RedBlueGames/TextTyper/Tests/TextTyperTester.cs similarity index 64% rename from Assets/RedBlueGames/TextTyper/Tests/Test_UGUI.cs rename to Assets/RedBlueGames/TextTyper/Tests/TextTyperTester.cs index 17453f1..d224407 100644 --- a/Assets/RedBlueGames/TextTyper/Tests/Test_UGUI.cs +++ b/Assets/RedBlueGames/TextTyper/Tests/TextTyperTester.cs @@ -9,11 +9,23 @@ /// /// Class that tests TextTyper and shows how to interface with it. /// - public class Test_UGUI : MonoBehaviour + public class TextTyperTester : MonoBehaviour { - public AudioClip printSoundEffect; - public Text text; - private Queue scripts = new Queue(); + [SerializeField] + private AudioClip printSoundEffect; + + [Header("UI References")] + + [SerializeField] + private Text text; + + [SerializeField] + private Button printNextButton; + + [SerializeField] + private Button printNoSkipButton; + + private Queue dialogueLines = new Queue(); [SerializeField] [Tooltip("The text typer element to test typing with")] @@ -24,28 +36,17 @@ public void Start() this.testTextTyper.PrintCompleted.AddListener(this.HandlePrintCompleted); this.testTextTyper.CharacterPrinted.AddListener(this.HandleCharacterPrinted); - scripts.Enqueue("When printing extremely long words, notice how they no longer wrap as they are printed."); - scripts.Enqueue("Hello! My name is... NPC. Got it, bub?"); - scripts.Enqueue("You can use uGUI text tag and color tag like this."); - scripts.Enqueue("bold text test bold text test"); - scripts.Enqueue("You can size 40 and size 20"); - scripts.Enqueue("You can color tag like this."); - ShowScript(); - } + this.printNextButton.onClick.AddListener(this.HandlePrintNextClicked); + this.printNoSkipButton.onClick.AddListener(this.HandlePrintNoSkipClicked); - public void OnClickWindow() - { - if (this.testTextTyper.IsSkippable()) - { - this.testTextTyper.Skip(); - } - else - { - ShowScript(); - } + dialogueLines.Enqueue("Hello! My name is... NPC. Got it, bub?"); + dialogueLines.Enqueue("You can use uGUI text tag and color tag like this."); + dialogueLines.Enqueue("bold text test bold text test"); + dialogueLines.Enqueue("You can size 40 and size 20"); + dialogueLines.Enqueue("You can color tag like this."); + ShowScript(); } - public void Update() { if (Input.GetKeyDown(KeyCode.Space)) @@ -64,14 +65,31 @@ public void Update() } } + private void HandlePrintNextClicked() + { + if (this.testTextTyper.IsSkippable()) + { + this.testTextTyper.Skip(); + } + else + { + ShowScript(); + } + } + + private void HandlePrintNoSkipClicked() + { + ShowScript(); + } + private void ShowScript() { - if (scripts.Count <= 0) + if (dialogueLines.Count <= 0) { return; } - this.testTextTyper.TypeText(scripts.Dequeue()); + this.testTextTyper.TypeText(dialogueLines.Dequeue()); } private void LogTag(RichTextTag tag) diff --git a/Assets/RedBlueGames/TextTyper/Tests/Test_UGUI.cs.meta b/Assets/RedBlueGames/TextTyper/Tests/TextTyperTester.cs.meta similarity index 100% rename from Assets/RedBlueGames/TextTyper/Tests/Test_UGUI.cs.meta rename to Assets/RedBlueGames/TextTyper/Tests/TextTyperTester.cs.meta diff --git a/Assets/RedBlueGames/TextTyper/TextTyper.cs b/Assets/RedBlueGames/TextTyper/TextTyper.cs index 68bb33c..9d0f00f 100644 --- a/Assets/RedBlueGames/TextTyper/TextTyper.cs +++ b/Assets/RedBlueGames/TextTyper/TextTyper.cs @@ -10,16 +10,13 @@ /// Type text component types out Text one character at a time. Heavily adapted from synchrok's GitHub project. /// [RequireComponent(typeof(Text))] - public class TextTyper : MonoBehaviour + public sealed class TextTyper : MonoBehaviour { /// /// The print delay setting. Could make this an option some day, for fast readers. /// private const float PrintDelaySetting = 0.02f; - private static readonly List UnityTagTypes = new List { "b", "i", "size", "color" }; - private static readonly List CustomTagTypes = new List { "delay" }; - // Characters that are considered punctuation in this language. TextTyper pauses on these characters // a bit longer by default. Could be a setting sometime since this doesn't localize. private readonly List punctutationCharacters = new List @@ -40,11 +37,8 @@ public class TextTyper : MonoBehaviour private Text textComponent; private string printingText; - private string displayedText; private float defaultPrintDelay; - private float overridePrintDelay; private Coroutine typeTextCoroutine; - private Stack outstandingTags; /// /// Gets the PrintCompleted callback event. @@ -90,14 +84,11 @@ private Text TextComponent /// Print delay (in seconds) per character. public void TypeText(string text, float printDelay = -1) { + this.Cleanup(); + this.defaultPrintDelay = printDelay > 0 ? printDelay : PrintDelaySetting; this.printingText = text; - if (this.typeTextCoroutine != null) - { - this.StopCoroutine(this.typeTextCoroutine); - } - this.typeTextCoroutine = this.StartCoroutine(this.TypeTextCharByChar(text)); } @@ -106,14 +97,11 @@ public void TypeText(string text, float printDelay = -1) /// public void Skip() { - if (this.typeTextCoroutine != null) - { - this.StopCoroutine(this.typeTextCoroutine); - this.typeTextCoroutine = null; - } + this.Cleanup(); - this.outstandingTags.Clear(); - this.TextComponent.text = RemoveCustomTags(this.printingText); + var generator = new TypedTextGenerator(); + var typedText = generator.GetCompletedText(this.printingText); + this.TextComponent.text = typedText.TextToPrint; this.OnTypewritingComplete(); } @@ -127,156 +115,43 @@ public bool IsSkippable() return this.typeTextCoroutine != null; } - /// - /// Called by Unity when the component is created. - /// - protected void Awake() - { - this.outstandingTags = new Stack(); - } - - private static string RemoveCustomTags(string text) + private void Cleanup() { - var textWithoutTags = text; - foreach (var tagType in CustomTagTypes) + if (this.typeTextCoroutine != null) { - textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, tagType); + this.StopCoroutine(this.typeTextCoroutine); + this.typeTextCoroutine = null; } - - return textWithoutTags; } private IEnumerator TypeTextCharByChar(string text) { - this.displayedText = string.Empty; this.TextComponent.text = string.Empty; - for (var i = 0; i < text.Length; i++) + var generator = new TypedTextGenerator(); + TypedTextGenerator.TypedText typedText; + int printedCharCount = 0; + do { - // Check for opening nodes - var remainingText = text.Substring(i, text.Length - i); - if (RichTextTag.StringStartsWithTag(remainingText)) - { - var tag = RichTextTag.ParseNext(remainingText); - this.ApplyTag(tag); - i += tag.Length - 1; - continue; - } - - // Add in the current character - var printedCharacter = text[i]; - this.displayedText += printedCharacter; + typedText = generator.GetTypedTextAt(text, printedCharCount); + this.TextComponent.text = typedText.TextToPrint; + this.OnCharacterPrinted(typedText.LastPrintedChar.ToString()); - // Hide the remaining characters - string hiddenText = remainingText.Remove(0, 1); - if (!string.IsNullOrEmpty(hiddenText)) - { - // Strip outstanding close tags because they confuse the end tags we will add - foreach (var tag in this.outstandingTags) - { - var firstOccurance = hiddenText.IndexOf(tag.ClosingTagText); - hiddenText = hiddenText.Remove(firstOccurance, tag.ClosingTagText.Length); - } - - // Remove color because you can't embed color nodes - hiddenText = RichTextTag.RemoveTagsFromString(hiddenText, "color"); - - // Add the transparent color around the string - hiddenText = RemoveCustomTags(hiddenText); - hiddenText = string.Concat("", hiddenText, ""); - } + ++printedCharCount; - // Close the ndoes that are outstanding - var printText = this.AddOutstandingClosingTagsToString(this.displayedText); - - // Apply the text - this.TextComponent.text = string.Concat(printText, hiddenText); - - this.OnCharacterPrinted(printedCharacter.ToString()); - - yield return new WaitForSeconds(this.GetPrintDelayForCharacter(printedCharacter)); + var delay = typedText.Delay > 0 ? typedText.Delay : this.GetPrintDelayForCharacter(typedText.LastPrintedChar); + yield return new WaitForSeconds(delay); } + while (!typedText.IsComplete); this.typeTextCoroutine = null; this.OnTypewritingComplete(); } - private void ApplyTag(RichTextTag tag) - { - // Push or Pop the tag from the outstanding tags stack. - if (tag.IsOpeningTag) - { - this.outstandingTags.Push(tag); - } - else - { - // Pop outstanding tag - var poppedTag = this.outstandingTags.Pop(); - if (poppedTag.TagType != tag.TagType) - { - var assertionMessage = string.Format( - "Popped TagType [{0}] did not match last outstanding tagType [{1}] " + - "in TypeText. Unity only respects tags that are added as a stack.", - poppedTag.TagType, - tag.TagType); - Debug.LogError(assertionMessage); - } - } - - // Execute Custom Tags here - if (tag.TagType == "delay") - { - try - { - this.overridePrintDelay = tag.IsOpeningTag ? float.Parse(tag.Parameter) : 0.0f; - } - catch (System.FormatException e) - { - var warning = string.Format( - "Invalid paramter format found in tag [{0}]. Parameter [{1}] does not parse to a float. Exception: {2}", - tag, - tag.Parameter, - e); - Debug.LogWarning(warning, this); - this.overridePrintDelay = 0.0f; - } - } - - // We only want to add in text of tags for elements that Unity will parse - // in its RichText enabled Text widget - if (UnityTagTypes.Contains(tag.TagType)) - { - this.displayedText += tag.TagText; - } - } - - private string AddOutstandingClosingTagsToString(string text) - { - var textWithTags = text; - - // We only need to add back in Unity tags, since they've been - // added to the text and Unity expects closing tags. - foreach (var tag in this.outstandingTags) - { - if (UnityTagTypes.Contains(tag.TagType)) - { - textWithTags = string.Concat(textWithTags, tag.ClosingTagText); - } - } - - return textWithTags; - } - private float GetPrintDelayForCharacter(char characterToPrint) { - // First obey overridePrintDelay when set - if (this.overridePrintDelay > 0.0f) - { - return this.overridePrintDelay; - } - // Then get the default print delay for the current character - float punctuationDelay = this.defaultPrintDelay * 4.0f; + float punctuationDelay = this.defaultPrintDelay * 8.0f; if (this.punctutationCharacters.Contains(characterToPrint)) { return punctuationDelay; diff --git a/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs b/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs new file mode 100644 index 0000000..8125730 --- /dev/null +++ b/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs @@ -0,0 +1,347 @@ +namespace RedBlueGames.Tools.TextTyper +{ + using System.Collections; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.Events; + using UnityEngine.UI; + + /// + /// Typed text generator is used to create TypedText results given a text that should be printed one character + /// at a time, up to the specified character. + /// + public sealed class TypedTextGenerator + { + private static readonly List UnityTagTypes = new List { "b", "i", "size", "color" }; + private static readonly List CustomTagTypes = new List { "delay" }; + + /// + /// Gets Completed TypedText from the specified text string. + /// + /// The completed text, as it should display in Unity. + /// Text to complete. + public TypedText GetCompletedText(string text) + { + var printText = RemoveCustomTags(text); + + var typedText = new TypedText(); + typedText.TextToPrint = printText; + typedText.Delay = 0.0f; + typedText.LastPrintedChar = printText[printText.Length - 1]; + typedText.IsComplete = true; + + return typedText; + } + + /// + /// Gets the typed text at the specified visibleCharacterIndex. This is the text that should be written + /// to the Text component. + /// + /// The generated at the specified visible character index. + /// Text to parse. + /// Visible character index (ignores tags). + public TypedText GetTypedTextAt(string text, int visibleCharacterIndex) + { + var textAsSymbolList = CreateSymbolListFromText(text); + + // Split the text into shown and hide strings based on the actual visible characters + int printedCharCount = 0; + var shownText = string.Empty; + var hiddenText = string.Empty; + var lastVisibleCharacter = char.MinValue; + foreach (var symbol in textAsSymbolList) + { + if (printedCharCount <= visibleCharacterIndex) + { + shownText += symbol.Text; + + // Keep track of the visible characters that have been printed + if (!symbol.IsTag) + { + lastVisibleCharacter = symbol.Character.ToCharArray()[0]; + } + } + else + { + hiddenText += symbol.Text; + } + + if (!symbol.IsTag) + { + printedCharCount++; + } + } + + var activeTags = GetActiveTagsInSymbolList(textAsSymbolList, visibleCharacterIndex); + + // Remove closing tags for active tags from hidden text (move to before color hide tag) + foreach (var activeTag in activeTags) + { + hiddenText = RemoveFirstOccurance(hiddenText, activeTag.ClosingTagText); + } + + // Remove all color tags from hidden text so that they don't cause it to be shown + // ex: This should be clear will show 'be clear" in red + hiddenText = RichTextTag.RemoveTagsFromString(hiddenText, "color"); + + // Add the hidden text, provided there is text to hide + if (!string.IsNullOrEmpty(hiddenText)) + { + var hiddenTag = RichTextTag.ClearColorTag; + hiddenText = hiddenText.Insert(0, hiddenTag.TagText); + hiddenText = hiddenText.Insert(hiddenText.Length, hiddenTag.ClosingTagText); + } + + // Add back in closing tags in reverse order + for (int i = 0; i < activeTags.Count; ++i) + { + hiddenText = hiddenText.Insert(0, activeTags[i].ClosingTagText); + } + + // Remove all custom tags since Unity will display them when printed (it doesn't recognize them as rich text tags) + var printText = shownText + hiddenText; + foreach (var customTag in CustomTagTypes) + { + printText = RichTextTag.RemoveTagsFromString(printText, customTag); + } + + // Calculate Delay, if active + var delay = 0.0f; + foreach (var activeTag in activeTags) + { + if (activeTag.TagType == "delay") + { + try + { + delay = activeTag.IsOpeningTag ? float.Parse(activeTag.Parameter) : 0.0f; + } + catch (System.FormatException e) + { + var warning = string.Format( + "TypedTextGenerator found Invalid paramter format in tag [{0}]. " + + "Parameter [{1}] does not parse to a float. Exception: {2}", + activeTag, + activeTag.Parameter, + e); + Debug.Log(warning); + delay = 0.0f; + } + } + } + + var typedText = new TypedText(); + typedText.TextToPrint = printText; + typedText.Delay = delay; + typedText.LastPrintedChar = lastVisibleCharacter; + typedText.IsComplete = string.IsNullOrEmpty(hiddenText); + + return typedText; + } + + private static List GetActiveTagsInSymbolList(List symbolList, int visibleCharacterPosition) + { + var activeTags = new List(); + int printableCharacterCount = 0; + foreach (var symbol in symbolList) + { + if (symbol.IsTag) + { + if (symbol.Tag.IsOpeningTag) + { + activeTags.Add(symbol.Tag); + } + else + { + var poppedTag = activeTags[activeTags.Count - 1]; + if (poppedTag.TagType != symbol.Tag.TagType) + { + var errorMessage = string.Format( + "TypedTextGenerator Popped TagType [{0}] that did not match last outstanding tagType [{1}]" + + ". Unity only respects tags that are added and closed as a stack.", + poppedTag.TagType, + symbol.Tag.TagType); + Debug.LogError(errorMessage); + } + + activeTags.RemoveAt(activeTags.Count - 1); + } + } + else + { + printableCharacterCount++; + + // Finished when we've passed the visibleCharacter (non-Tag) position + if (printableCharacterCount > visibleCharacterPosition) + { + break; + } + } + } + + return activeTags; + } + + private static List CreateSymbolListFromText(string text) + { + var symbolList = new List(); + int parsedCharacters = 0; + while (parsedCharacters < text.Length) + { + TypedTextSymbol symbol = null; + + // Check for tags + var remainingText = text.Substring(parsedCharacters, text.Length - parsedCharacters); + if (RichTextTag.StringStartsWithTag(remainingText)) + { + var tag = RichTextTag.ParseNext(remainingText); + symbol = new TypedTextSymbol(tag); + } + else + { + symbol = new TypedTextSymbol(remainingText.Substring(0, 1)); + } + + parsedCharacters += symbol.Length; + symbolList.Add(symbol); + } + + return symbolList; + } + + private static char GetLastVisibleCharInSymbolList(List symbolList) + { + for (int i = symbolList.Count - 1; i >= 0; --i) + { + var symbol = symbolList[i]; + if (!symbol.IsTag) + { + return symbol.Character.ToCharArray()[0]; + } + } + + return char.MinValue; + } + + private static string RemoveAllTags(string textWithTags) + { + string textWithoutTags = textWithTags; + textWithoutTags = RemoveUnityTags(textWithoutTags); + textWithoutTags = RemoveCustomTags(textWithoutTags); + + return textWithoutTags; + } + + private static string RemoveCustomTags(string textWithTags) + { + string textWithoutTags = textWithTags; + foreach (var customTag in CustomTagTypes) + { + textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, customTag); + } + + return textWithoutTags; + } + + private static string RemoveUnityTags(string textWithTags) + { + string textWithoutTags = textWithTags; + foreach (var unityTag in UnityTagTypes) + { + textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, unityTag); + } + + return textWithoutTags; + } + + private static string RemoveFirstOccurance(string source, string searchString) + { + var index = source.IndexOf(searchString); + if (index >= 0) + { + return source.Remove(index, searchString.Length); + } + else + { + return source; + } + } + + /// + /// TypedText represents results from the TypedTextGenerator + /// + public class TypedText + { + /// + /// Gets or sets the text to print to the Text component. This is what is visible to the user. + /// + /// The text to print. + public string TextToPrint { get; set; } + + /// + /// Gets or sets the desired Delay based on the delay tags in the typed text. + /// + /// The delay. + public float Delay { get; set; } + + /// + /// Gets or sets the last printed char. + /// + /// The last printed char. + public char LastPrintedChar { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is complete and has printed all its characters) + /// + /// true if this instance is complete; otherwise, false. + public bool IsComplete { get; set; } + } + + private class TypedTextSymbol + { + public TypedTextSymbol(string character) + { + this.Character = character; + } + + public TypedTextSymbol(RichTextTag tag) + { + this.Tag = tag; + } + + public string Character { get; private set; } + + public RichTextTag Tag { get; private set; } + + public int Length + { + get + { + return this.Text.Length; + } + } + + public string Text + { + get + { + if (this.IsTag) + { + return this.Tag.TagText; + } + else + { + return this.Character; + } + } + } + + public bool IsTag + { + get + { + return this.Tag != null; + } + } + } + } +} \ No newline at end of file diff --git a/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs.meta b/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs.meta new file mode 100644 index 0000000..98f83ec --- /dev/null +++ b/Assets/RedBlueGames/TextTyper/TypedTextGenerator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 067e8034ef9f449c6bfb7b6fe26b0a3c +timeCreated: 1488807547 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: