当前位置: 首页 > news >正文

Unity Text打字机效果,支持富文本

将之前的代码给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;}}
}

效果
测试代码
在这里插入图片描述

相关文章:

  • C++ 与 Lua 联合编程
  • [预备知识]6. 优化理论(二)
  • 如何配置NGINX作为反向代理服务器来缓存后端服务的响应?
  • 微信小程序 自定义组件 标签管理
  • [SoC]AXI总线Performance验证方案
  • 【AI面试准备】Git与CI/CD及单元测试实战指南
  • [Linux]从零开始的STM32MP157 Buildroot根文件系统构建
  • mindyolo填坑
  • 如何利用dify 生成Fine‑tune 需要的Alpaca 格式数据
  • 正则表达式与文本三剑客grep、sed、awk
  • linux指令中的竖线(“|”)是干啥的?【含实例展示】
  • 数据库系统概论|第五章:数据库完整性—课程笔记1
  • 【服务器通信-socket】——int socket(int domain, int type, int protocol);
  • DBeaver连接人大金仓数据库V9
  • 上位机知识篇---PSRAM和RAM
  • 【算法基础】三指针排序算法 - JAVA
  • Google机器学习系列 - 监督学习
  • 50.【必备】二分答案法与相关题目
  • 玩转Docker(一):基本概念
  • STM32——GPIO
  • 从“土”到“潮”,唢呐何以“圈粉”年轻人
  • 这座“蚌埠住了”的城市不仅会接流量,也在努力成为文旅实力派
  • 央媒关注给保洁人员设休息室:让每一份踏实奋斗得到尊重呵护
  • 央视曝光假进口保健品:警惕!保税仓发货不等于真进口
  • 闲暇时间的“堕落”
  • 著名统计学家、北京工业大学应用数理学院首任院长王松桂逝世