将之前的代码给deepseek跑了一下,感觉优化的很不错。效果与预期相符。
能正确显示和解析富文本
TypewriterEffect.cs
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MYTOOL.UI
{
[DisallowMultipleComponent]
[RequireComponent(typeof(UnityEngine.UI.Text))]
public class TypewriterEffect : MonoBehaviour
{
[SerializeField, Tooltip("默认打字速度(每个字符的间隔时间)")]
private float defaultTypeInterval = 0.05f;
private UnityEngine.UI.Text targetTextComponent;
private readonly StringBuilder typewriterBuilder = new StringBuilder();
private string processedText;
private Coroutine typingCoroutine;
private Action onCompleteCallback;
private static readonly string[] uguiRichTextTags = { "b", "i", "size", "color" };
private readonly Stack<string> activeTags = new Stack<string>();
private void Awake()
{
ValidateTextComponent();
}
public void StartTyping(string content, float speed = -1)
{
ValidateTextComponent();
PrepareForNewTyping(content, speed);
typingCoroutine = StartCoroutine(TypingRoutine(content));
}
public void SkipTyping()
{
if (!IsTyping) return;
StopCoroutine(typingCoroutine);
typingCoroutine = null;
targetTextComponent.text = processedText;
activeTags.Clear();
onCompleteCallback?.Invoke();
}
public bool IsTyping => typingCoroutine != null;
public void SetCompleteCallback(Action callback)
{
onCompleteCallback = callback;
}
private void ValidateTextComponent()
{
if (targetTextComponent == null)
{
targetTextComponent = GetComponent<UnityEngine.UI.Text>();
if (targetTextComponent == null)
{
throw new MissingComponentException("Text component is required for TypewriterEffect");
}
}
}
private void PrepareForNewTyping(string content, float speed)
{
defaultTypeInterval = speed > 0 ? speed : defaultTypeInterval;
processedText = ProcessSpeedTags(content);
targetTextComponent.text = string.Empty;
activeTags.Clear();
if (typingCoroutine != null)
{
StopCoroutine(typingCoroutine);
}
}
private IEnumerator TypingRoutine(string originalText)
{
typewriterBuilder.Clear();
float currentSpeed = defaultTypeInterval;
for (int index = 0; index < originalText.Length; index++)
{
if (TryProcessSpeedTag(originalText, ref index, out float newSpeed))
{
currentSpeed = newSpeed;
continue;
}
if (TryProcessRichTextTag(originalText, ref index))
{
UpdateTextDisplay();
continue;
}
typewriterBuilder.Append(originalText[index]);
UpdateTextDisplay();
yield return new WaitForSeconds(currentSpeed);
}
FinalizeTyping();
}
private bool TryProcessSpeedTag(string text, ref int index, out float speed)
{
speed = defaultTypeInterval;
const string speedTagOpen = "[speed=";
const string speedTagClose = "[/speed]";
// 处理开标签
if (text[index] == '[' && text.Length > index + speedTagOpen.Length && text.Substring(index, speedTagOpen.Length) == speedTagOpen)
{
int closingBracketIndex = text.IndexOf(']', index);
if (closingBracketIndex == -1) return false;
string speedValue = text.Substring(index + speedTagOpen.Length, closingBracketIndex - index - speedTagOpen.Length);
if (float.TryParse(speedValue, out speed))
{
index = closingBracketIndex;
return true;
}
}
// 处理闭标签
if (text.Length > index + speedTagClose.Length && text.Substring(index, speedTagClose.Length) == speedTagClose)
{
speed = defaultTypeInterval;
index += speedTagClose.Length - 1;
return true;
}
return false;
}
private bool TryProcessRichTextTag(string text, ref int index)
{
if (text[index] != '<') return false;
// 处理开标签
foreach (var tag in uguiRichTextTags)
{
string tagStart = $"<{tag}";
if (text.Length > index + tagStart.Length && text.Substring(index, tagStart.Length) == tagStart)
{
int closingIndex = text.IndexOf('>', index);
if (closingIndex == -1) return false;
string fullTag = text.Substring(index, closingIndex - index + 1);
typewriterBuilder.Append(fullTag);
activeTags.Push(tag);
index = closingIndex; // 跳过整个标签内容
return true;
}
}
// 处理闭标签
foreach (var tag in uguiRichTextTags)
{
string tagEnd = $"</{tag}>";
if (text.Length >= index + tagEnd.Length && text.Substring(index, tagEnd.Length) == tagEnd)
{
if (activeTags.Count > 0 && activeTags.Peek() == tag)
{
activeTags.Pop();
}
typewriterBuilder.Append(tagEnd);
index += tagEnd.Length - 1; // 跳过整个闭合标签
return true;
}
}
return false;
}
private void UpdateTextDisplay()
{
targetTextComponent.text = typewriterBuilder.ToString() + GenerateActiveTagsClosure();
}
private string GenerateActiveTagsClosure()
{
StringBuilder closure = new StringBuilder();
foreach (var tag in activeTags)
{
closure.Append($"</{tag}>");
}
return closure.ToString();
}
private void FinalizeTyping()
{
typingCoroutine = null;
onCompleteCallback?.Invoke();
}
private static string ProcessSpeedTags(string input)
{
return System.Text.RegularExpressions.Regex.Replace(input, @"\[speed=[^\]]+\]", string.Empty, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
}
}
public static class TypewriterExtensions
{
public static void StartTypewriter(this UnityEngine.UI.Text textComponent, string content, float speed = 0.05f, Action onComplete = null)
{
TypewriterEffect effect = GetOrAddTypewriterComponent(textComponent);
effect.StartTyping(content, speed);
effect.SetCompleteCallback(onComplete);
}
public static void SkipTypewriter(this UnityEngine.UI.Text textComponent)
{
if (textComponent.TryGetComponent<TypewriterEffect>(out var effect)) effect.SkipTyping();
}
public static bool IsTyping(this UnityEngine.UI.Text textComponent)
{
TypewriterEffect effect = textComponent.GetComponent<TypewriterEffect>();
return effect != null && effect.IsTyping;
}
private static TypewriterEffect GetOrAddTypewriterComponent(UnityEngine.UI.Text textComponent)
{
if (!textComponent.TryGetComponent<TypewriterEffect>(out var effect))
{
effect = textComponent.gameObject.AddComponent<TypewriterEffect>();
}
return effect;
}
}
}
效果