추가
기능이 있는 줄 몰랐는데 DOTween 에 DOText 라는 기능이 있다. 타이핑, Rich Text가 적용되니 DOTween을 쓰면 해당 기능을 쓰면 될 듯 ( TextMeshPro는 DOTween Pro 만 가능 )
유니티 UGUI 의 Text 컴포넌트의 타이핑 효과를 처리하는 스크립트.
표시하려는 UI의 텍스트 내용의 중간이 볼드로 강조 혹은 색이 다르게 적용해야 되는 상황.
사용하는 UI가 UGUI이기 때문에 Text 컴포넌트의 Rich Text 기능을 사용.
참조: https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/StyledText.html
타이핑 효과를 주는 부분은 구현하는 난이도가 낮은 편이지만 Rich Text를 적용할 경우 단순히 일정 간격마다 텍스트 내용을 추가하는 것으로는 처리가 불가능하다.
(Rich Text는 <b>볼드</b> 라는 괄호로 열고 닫아야 적용이 되기 때문에 텍스트 내용을 하나씩 추가하게 될 경우 닫는 괄호가 적용되기 이전까지는 괄호의 내용까지 전부 보인다.)
검색을 통해 쉽게 나오는 타이핑 효과 스크립트들의 내용을 살펴보았지만 아쉽게도 여러 효과를 중첩해서 적용하는 것에 대한 처리는 발견하지 못하여 검색해서 나온 Rich Text 타이핑 처리가 가능한 스크립트를 참조하여 스크립트를 작성하였다.
(Rich Text를 적용할 때 <b><i>볼드와 이탤릭</i>볼드</b> 라는 방식으로 2개 이상의 효과를 중첩해서 적용이 가능하다.)
사용하는 방법은 Rich Text를 사용하는 것처럼 내용을 미리 작성한 뒤 Enable이 되자마자 처리하거나 메소드에 파라미터로 Text 값을 넣어서 타이핑을 시작하게 만들면 된다.
Rich Text 처리에 제약이 있다면 미리 정의된 color 태그 값(위의 Rich Text 관련 URL의 Supported colors 관련)에 대한 처리는 구현되어 있지 않으며 16진수 값을 입력하는 방식만 가능하다. 16진수 색상값은 '16진수 색상표' 정도로 검색하면 바로 나오니 사용하는데 큰 어려움은 없다.
Rich Text를 타이핑 처리하는 방식은 괄호를 열고 닫는 것을 확인하여 효과의 상태를 true/false 처리를 하고 괄호는 텍스트에 추가하지 않고 넘어간 뒤 각 문자마다 현재의 효과 상태에 따라 Rich Text 효과를 전부 적용하는 방식이다.
(<b>AB<i>CD</i></b> 라는 내용은 <b>A</b><b>B</b><i><b>C</b></i><i><b>D</b></i> 로 처리를 하여 하나씩 타이핑 효과를 준다)
따라서 타이핑 처리되는 동안에는 실제 텍스트 값이 매우 길어질 수 있다. 끝까지 타이핑을 하면 최초 입력된 내용으로 적용하는 코드가 타이핑 처리 끝부분에 있지만 아래의 문제점들 중 첫번째 문제 때문에 주석 처리를 하였다.
문제점
문제점이 있다면 Rich Text가 중첩되는 것을 처리하는 것이 유일한 목표였기 때문에 Unity의 Rich Text 처리와는 약간 다른 기준으로 처리가 되고 있다.
1. 괄호를 열고 닫는 순서가 맞지 않아도 열고 닫기만 하면 적용된다.
( <b><i> 볼드 이탤릭 </b></i> 라고 작성해도 볼드와 이탤릭이 적용된다.)
2. Rich Text가 true일 경우 지원되는 효과의 괄호 내용은 적용 여부와 상관없이 전부 제외되고 표시된다.
( <b>볼드 라고 작성할 경우 '볼드' 라고 표시 )
스크립트
private Text text;
private string inputText;
[SerializeField]
private float typingSpeed;
[SerializeField]
private bool enableTyping;
private bool isTyping;
private readonly string[] richTextSymbols = { "b", "i" };
private readonly string[] richTextCloseSymbols = { "b", "i", "size", "color" };
public bool IsTyping => isTyping;
private void Awake()
{
Initialize();
}
public void OnEnable()
{
if(enableTyping)
{
TextTyping(text.text);
}
}
public void OnDisable()
{
text.text = inputText;
}
private void Initialize()
{
text = GetComponent<Text>();
}
public void TextTyping(string text)
{
if(isTyping)
{
StopAllCoroutines();
isTyping = false;
}
StartCoroutine(CoTextTyping(text));
}
private IEnumerator CoTextTyping(string text)
{
isTyping = true;
WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
int typingIndex = 0;
float nowWaitTime = 0f;
char[] splitTypingTexts = text.ToCharArray();
StringBuilder stringBuilder = new StringBuilder();
bool bold = false;
bool italic = false;
bool size = false;
bool color = false;
int fontSize = 0;
string colorCode = null;
inputText = text;
this.text.text = null;
while(true)
{
yield return waitForEndOfFrame;
nowWaitTime += Time.deltaTime;
if(typingIndex == splitTypingTexts.Length)
{
break;
}
if(nowWaitTime >= typingSpeed)
{
if(this.text.supportRichText)
{
bool symbolCatched = false;
for(int i = 0; i < richTextSymbols.Length; i++)
{
string symbol = string.Format("<{0}>", richTextSymbols[i]);
string closeSymbol = string.Format("</{0}>", richTextCloseSymbols[i]);
if(splitTypingTexts[typingIndex] == '<' && typingIndex + (1 + richTextSymbols[i].Length) < text.Length && text.Substring(typingIndex, 2 + richTextSymbols[i].Length).Equals(symbol))
{
switch(richTextSymbols[i])
{
case "b":
typingIndex += symbol.Length;
if(typingIndex + closeSymbol.Length + 3 <= text.Length)
{
if(text.Substring(typingIndex, text.Length - typingIndex).Contains(closeSymbol))
{
bold = true;
symbolCatched = true;
}
}
break;
case "i":
typingIndex += symbol.Length;
if (typingIndex + closeSymbol.Length + 3 <= text.Length)
{
if (text.Substring(typingIndex, text.Length - typingIndex).Contains(closeSymbol))
{
italic = true;
symbolCatched = true;
}
}
break;
}
}
}
if(splitTypingTexts[typingIndex] == '<' && typingIndex + 14 < text.Length && text.Substring(typingIndex, 8).Equals("<color=#") && splitTypingTexts[typingIndex + 14] == '>')
{
string closeSymbol = string.Format("</{0}>", "color");
string tempColorCode = text.Substring(typingIndex + 8, 6);
typingIndex += 15;
if(typingIndex + closeSymbol.Length <= text.Length)
{
if(text.Substring(typingIndex, text.Length - typingIndex).Contains(closeSymbol))
{
color = true;
colorCode = tempColorCode;
symbolCatched = true;
}
}
}
if(splitTypingTexts[typingIndex] == '<' && typingIndex + 6 < text.Length && text.Substring(typingIndex, 6).Equals("<size="))
{
string closeSymbol = string.Format("</{0}>", "size");
string sizeSub = text.Substring(typingIndex + 6);
int symbolIndex = sizeSub.IndexOf('>');
string tempSize = sizeSub.Substring(0, symbolIndex);
typingIndex += 7 + tempSize.Length;
if(typingIndex + closeSymbol.Length <= text.Length)
{
if(text.Substring(typingIndex, text.Length - typingIndex).Contains(closeSymbol))
{
size = true;
fontSize = int.Parse(tempSize);
symbolCatched = true;
}
}
}
bool closeSymbolCatched = false;
for(int i = 0; i < richTextCloseSymbols.Length; i++)
{
string closeSymbol = string.Format("</{0}>", richTextCloseSymbols[i]);
if(splitTypingTexts[typingIndex] == '<' && typingIndex + (1 + richTextCloseSymbols[i].Length) < text.Length && text.Substring(typingIndex, 3 + richTextCloseSymbols[i].Length).Equals(closeSymbol))
{
switch(richTextCloseSymbols[i])
{
case "b":
typingIndex += closeSymbol.Length;
bold = false;
closeSymbolCatched = true;
break;
case "i":
typingIndex += closeSymbol.Length;
italic = false;
closeSymbolCatched = true;
break;
case "size":
typingIndex += closeSymbol.Length;
size = false;
fontSize = 0;
closeSymbolCatched = true;
break;
case "color":
typingIndex += closeSymbol.Length;
color = false;
colorCode = null;
closeSymbolCatched = true;
break;
}
}
if(closeSymbolCatched)
{
break;
}
}
if(symbolCatched || closeSymbolCatched)
{
continue;
}
if(typingIndex < text.Length)
{
string convertedRichText = RichTextConvert(splitTypingTexts[typingIndex].ToString(), bold, italic, size, fontSize, color, colorCode);
stringBuilder.Append(convertedRichText);
}
}
else
{
if(typingIndex < text.Length)
{
stringBuilder.Append(splitTypingTexts[typingIndex]);
}
}
this.text.text = stringBuilder.ToString();
typingIndex++;
nowWaitTime = 0f;
}
}
//타이핑이 끝났을 때 최초 입력 값으로 적용시키기
//this.text.text = inputText;
isTyping = false;
}
private string RichTextConvert(string text, bool bold, bool italic, bool size, int fontSize, bool color, string colorCode)
{
StringBuilder stringBuilder = new StringBuilder();
if (bold)
{
stringBuilder.Append("<b>");
stringBuilder.Append(text);
stringBuilder.Append("</b>");
text = stringBuilder.ToString();
stringBuilder.Clear();
}
if (italic)
{
stringBuilder.Append("<i>");
stringBuilder.Append(text);
stringBuilder.Append("</i>");
text = stringBuilder.ToString();
stringBuilder.Clear();
}
if (color)
{
stringBuilder.Append("<color=#");
stringBuilder.Append(colorCode);
stringBuilder.Append(">");
stringBuilder.Append(text);
stringBuilder.Append("</color>");
text = stringBuilder.ToString();
stringBuilder.Clear();
}
if (size)
{
stringBuilder.Append("<size=");
stringBuilder.Append(fontSize);
stringBuilder.Append(">");
stringBuilder.Append(text);
stringBuilder.Append("</size>");
text = stringBuilder.ToString();
stringBuilder.Clear();
}
return text;
}
참조: https://github.com/synchrok/TypeText
'Unity Engine' 카테고리의 다른 글
Unity TextMeshPro TMP_FontAsset AssetBundle 사용 시 주의점 (0) | 2021.07.21 |
---|---|
ScriptableObject AssetBundle 참조 및 불러오기 실패 문제 (0) | 2021.03.17 |
C# 람다(Lambda) : Button onClick을 반복문에서 동적 할당 시 지역 변수 참조 문제 (0) | 2019.10.10 |
UnityWebRequest: Delete Request DownloadHandler NULL (0) | 2019.08.07 |
Unity Android Build: AndroidManifest Multiple 시 OBB파일을 불러오지 못하는 문제 (0) | 2019.06.27 |