Unity TextMeshPro段落级渐变色实现:基于UV映射的高效方案
2026/7/21 13:07:21 网站建设 项目流程

1. 项目概述:为什么我们需要段落级渐变色?

在Unity的UI开发中,尤其是使用UGUI或TextMeshPro时,我们经常遇到一个看似简单却令人头疼的需求:如何让一大段文字拥有丰富的色彩变化?默认情况下,无论是UGUI的Text还是TextMeshPro,都只支持对单个字符应用顶点色,或者通过Shader实现全局的、基于UV的渐变。但当我们想要实现“从这一段开始是蓝色,过渡到那一段变成红色”这种基于语义段落、而非单个字符或整个文本框的渐变色效果时,常规方法就捉襟见肘了。

我接手过一个项目,需要制作一个视觉小说风格的对话界面。主角的台词需要根据情绪变化,从冷静的深蓝色渐变为激动的橙红色。如果对整个文本框应用一个从左到右的渐变,当台词换行时,颜色过渡会变得支离破碎,完全破坏了情绪表达的连贯性。这就是“段落级”渐变色的核心价值所在——它让颜色的变化与文本的语义结构(段落、句子)同步,而不是被UI元素的物理布局(文本框的宽高)所束缚。

传统的解决方案无外乎几种:把文字拆成多个GameObject分别上色(维护噩梦)、使用富文本标签逐字标记(效率低下且不灵活)、或者编写复杂的Shader根据字符索引计算颜色(实现复杂且难以动态控制)。直到我深入研究了TextMeshPro的贴图(Atlas)和材质系统,才发现了一条更优雅、更高效的道路:利用TextMeshPro对每个字符独立采样贴图的特性,通过动态生成并应用一张渐变贴图,并将贴图的UV映射与文本的段落布局关联起来,从而实现精准的段落级色彩控制。

这个方法听起来有点绕,但原理其实很直观。想象一下,TextMeshPro渲染的每个字符,本质上都是从一张大贴图(字体图集)上“抠”下来一小块进行显示。如果我们能控制每个字符采样贴图时所用的“坐标”(即UV),并且我们准备的贴图本身就是一张从上到下或从左到右的渐变色图,那么通过程序动态地调整不同段落字符的UV坐标范围,就能让它们分别采样到渐变贴图上不同区段的颜色。这样一来,一段文字的所有字符共享同一张渐变贴图,但通过UV偏移,就能呈现出段落间的颜色差异和段落内的平滑过渡。

2. 核心思路与方案选型:为什么是贴图UV控制?

在决定采用贴图UV控制方案前,我系统性地评估了市面上常见的几种给TextMeshPro文字上色的方法,每一种都有其明显的局限性,而我们的“段落级渐变色”需求恰好放大了这些缺点。

2.1 常见方案对比与淘汰原因

  • 方案一:UGUI/TextMeshPro顶点色逐字修改

    • 原理:直接操作TMP_TexttextInfo.characterInfo数组,遍历每个字符的四个顶点,修改其color属性。
    • 缺点:这是最“重”的操作。每次文本更新(包括内容、字体大小、布局变化)都需要重新计算并修改大量顶点数据(一段话可能有成百上千个顶点)。对于需要频繁更新或长文本的场景,性能开销巨大。更重要的是,它难以实现平滑的“渐变”,因为你需要为每个顶点计算一个独立的颜色值,逻辑复杂且不直观。
  • 方案二:使用Rich Text颜色标签

    • 原理:在文本中嵌入``这样的标签,TextMeshPro会解析并应用颜色。
    • 缺点:这是“段落级”需求的死穴。颜色标签的作用范围是标签之后的文本,直到被下一个颜色标签覆盖。这意味着你必须精确知道每个段落的起止位置,并手动插入大量标签。如果文本是动态生成的(比如从配置表读取),或者需要根据内容长度自动换行,维护这些标签将是一场灾难。它也无法实现一个段落内部的平滑渐变。
  • 方案三:编写自定义Shader实现基于位置的渐变

    • 原理:写一个Surface Shader或Unlit Shader,在片段着色器中根据字符在文本框中的归一化位置(如x坐标)来插值两种颜色。
    • 缺点:这是“文本框级”渐变,而非“段落级”。其颜色变化完全依赖于字符在矩形文本框内的相对位置。一旦文本换行,第二行开头的颜色会“跳回”渐变起点,导致视觉上的不连续。下图清晰地展示了这个问题:
    • [假设此处有一张对比图,左侧是理想的段落渐变(颜色随段落流动),右侧是基于文本框UV的渐变(换行处颜色断裂)]
    • 虽然可以通过传递更多信息(如行索引、字符索引)给Shader来尝试修复,但Shader会变得极其复杂,且动态控制性差。

2.2 胜出方案:贴图UV控制法的核心优势

经过对比,利用TextMeshPro的贴图UV控制方案脱颖而出,因为它完美地解决了上述痛点:

  1. 性能高效:核心操作发生在文本生成时,即修改字符的UV坐标。这是一次性的CPU计算,渲染时GPU只需采样一张现成的贴图,没有额外的逐顶点颜色计算或复杂的Shader逻辑。
  2. 实现真正的段落级控制:我们可以根据字符所属的段落索引,为其UV坐标施加一个统一的偏移量。同一个段落的所有字符共享一个UV偏移,从而采样到渐变贴图的同一颜色区域;不同段落的UV偏移不同,从而采样到不同区域。颜色过渡与文本的语义结构紧密绑定。
  3. 动态与灵活:渐变贴图可以预先制作好,也可以在运行时动态生成(例如,根据游戏内情绪值生成从冷色到暖色的渐变)。通过简单地替换贴图或调整UV映射算法,就能实现各种复杂的色彩效果,而无需改动文本内容或Shader。
  4. 与TextMeshPro生态无缝集成:TextMeshPro本身就是为了高质量文本渲染而设计的,其底层就是基于贴图(字体图集)渲染。我们的方法只是“借用”了这套成熟机制,为其增加了另一层“内容贴图”,因此稳定性极高,兼容所有TextMeshPro特性(如字距调整、富文本基线等)。

注意:此方案需要你对TextMeshPro的TMP_TextInfo数据结构有一定的了解,因为它需要遍历并修改文本信息中的字符数据。不过别担心,接下来的实操部分会一步步拆解。

3. 实战准备:构建渐变贴图与基础场景

理论说得再多,不如动手做一遍。我们先从最基础的准备工作开始。

3.1 创建与配置渐变贴图

渐变贴图是我们整个效果的“颜料盘”。它的质量直接决定了最终渐变的平滑程度。

  1. 使用Photoshop/GIMP等工具创建

    • 新建一个纹理文件。宽度建议为4的倍数(如256、512),高度为1。为什么高度是1?因为对于水平方向的段落渐变(颜色从左到右变化),我们只需要水平方向有颜色变化,垂直方向是单一的。这样在采样时,V坐标(垂直方向)可以固定为0.5,只变化U坐标(水平方向)。
    • 使用渐变工具,填充你想要的色彩序列。例如,从左到右可以是#3498db(蓝) ->#9b59b6(紫) ->#e74c3c(红)。
    • 将文件保存为PNG格式,导入Unity。
  2. 在Unity中配置导入设置

    • 将PNG图片导入Unity后,选中它,在Inspector面板中做如下关键设置:
      • Texture Type: 选择Sprite (2D and UI)。虽然我们不是当Sprite用,但这个类型能确保纹理以非压缩的RGBA格式存在,颜色精度最高。
      • Generate Mip Maps:取消勾选。UI纹理通常不需要Mipmap,关闭可以节省内存并避免远处模糊。
      • Wrap Mode: 选择Clamp。这可以防止在UV坐标轻微超出[0,1]范围时,颜色采样到贴图另一端的意外情况。
      • Filter Mode: 选择Bilinear即可,保证渐变平滑。
    • 点击Apply
  3. (可选)运行时动态生成贴图:如果你的渐变需要根据游戏状态动态变化(比如血量越低,文字越红),可以在运行时用代码创建Texture2D并填充像素。

    // 示例:创建一个256x1的从黑到红的渐变贴图 int width = 256; int height = 1; Texture2D gradientTex = new Texture2D(width, height, TextureFormat.RGBA32, false); gradientTex.wrapMode = TextureWrapMode.Clamp; for (int x = 0; x < width; x++) { float t = (float)x / (width - 1); Color color = Color.Lerp(Color.black, Color.red, t); for (int y = 0; y < height; y++) { gradientTex.SetPixel(x, y, color); } } gradientTex.Apply(); // 必须调用Apply才能使更改生效

3.2 设置TextMeshPro与材质

  1. 在Unity场景中创建一个UI Canvas。

  2. 右键Canvas -> UI -> Text - TextMeshPro,创建一个TMP文本对象,命名为TMP_GradientText

  3. 我们需要一个专用的材质来使用我们的渐变贴图。最简单的方法是复制一个TextMeshPro自带的材质进行修改。

    • 在Project窗口中,找到TextMeshPro/Resources/Shaders目录,复制TMP_SDF-Mobile.shader(或TMP_SDF.shader,根据项目平台选择)对应的材质球,或者直接复制你刚创建的TMP文本对象上的材质实例。
    • 将复制出的新材质命名为Mat_TMP_Gradient
    • 选中这个新材质,在Inspector中,你会看到它的Shader属性有很多参数。我们需要关注的是Face部分下的Face ColorTexture
    • 关键操作:将我们准备好的渐变贴图,拖拽到材质Face部分的Texture槽中。此时,文本可能会变成一片纯色,这是因为默认的Face Color是白色,它会与贴图颜色相乘。为了让贴图颜色完全显示,我们需要将Face Color设置为纯白色(RGB 255,255,255)。同时,确保Vertex Color参数对颜色的影响也设置为纯白(通常来自文本的color属性)。
  4. 将我们刚配置好的Mat_TMP_Gradient材质,拖拽赋值给场景中TMP_GradientText对象的Font Material属性。

此时,如果你输入文字,可能会发现所有文字都显示了渐变贴图最左侧那一竖条的颜色。这是因为目前所有字符的UV坐标都默认指向贴图的(0,0)位置。接下来,我们就需要通过代码来动态调整这些UV坐标。

4. 核心实现:解析文本与动态映射UV

这是整个方案最核心的代码部分。我们需要编写一个脚本,挂载到TMP文本对象上,它负责在文本更新时,分析段落结构,并为每个字符计算合适的UV坐标。

4.1 脚本结构与初始化

首先创建一个C#脚本,命名为TMPParagraphGradient

using TMPro; using UnityEngine; [RequireComponent(typeof(TMP_Text))] public class TMPParagraphGradient : MonoBehaviour { [SerializeField] private Texture2D gradientTexture; // 拖入我们准备的渐变贴图 private TMP_Text m_TextComponent; private Material m_MaterialInstance; // 材质实例 void Awake() { m_TextComponent = GetComponent<TMP_Text>(); // 重要:获取材质的实例,避免修改影响到共享材质 m_MaterialInstance = m_TextComponent.fontMaterial; // 将渐变贴图赋给材质实例 if (gradientTexture != null && m_MaterialInstance != null) { m_MaterialInstance.SetTexture("_FaceTex", gradientTexture); // 对于TMP Shader,主贴图属性名可能是_MainTex,需要根据实际Shader查看 // 更通用的方法是:m_MaterialInstance.mainTexture = gradientTexture; } } void OnEnable() { // 订阅文本变更事件,确保文本每次更新(包括通过代码设置text)后都重新应用渐变 TMPro_EventManager.TEXT_CHANGED_EVENT.Add(OnTextChanged); // 初始应用一次 ApplyGradientToParagraphs(); } void OnDisable() { TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(OnTextChanged); } void OnTextChanged(Object obj) { // 确保只有当前对象的文本变化时才触发,避免性能浪费 if (obj == m_TextComponent) ApplyGradientToParagraphs(); } }

4.2 段落检测与UV映射算法

现在实现最关键的ApplyGradientToParagraphs方法。这里的逻辑是:遍历所有字符,根据换行符(\n)来划分段落,然后为每个段落分配渐变贴图上的一段UV范围。

private void ApplyGradientToParagraphs() { if (m_TextComponent == null || gradientTexture == null) return; // 强制TextMeshPro生成最新的网格和文本信息 m_TextComponent.ForceMeshUpdate(); TMP_TextInfo textInfo = m_TextComponent.textInfo; if (textInfo.characterCount == 0) return; // 1. 首先,我们需要找出所有段落的起止索引 List<(int start, int length)> paragraphs = new List<(int, int)>(); int currentParagraphStart = 0; string rawText = m_TextComponent.text; for (int i = 0; i <= rawText.Length; i++) { // 如果遇到换行符,或者已经是文本末尾,则一个段落结束 if (i == rawText.Length || rawText[i] == '\n') { int paragraphLength = i - currentParagraphStart; if (paragraphLength > 0) // 忽略空段落(连续换行) { paragraphs.Add((currentParagraphStart, paragraphLength)); } currentParagraphStart = i + 1; // 下一个字符是下一段的开始 } } int totalParagraphs = paragraphs.Count; if (totalParagraphs == 0) return; // 没有有效段落 // 2. 计算每个字符的UV偏移 // 假设我们使用渐变贴图的水平方向,将贴图平均分给每个段落 float uvStepPerParagraph = 1.0f / totalParagraphs; for (int paraIndex = 0; paraIndex < totalParagraphs; paraIndex++) { var (paraStartCharIndex, paraLength) = paragraphs[paraIndex]; // 当前段落对应的UV范围的中心U值(我们让一个段落的字符都采样这个U值附近,实现纯色段落) // 如果想要段落内也有渐变,可以计算基于字符在段落内位置的偏移,这里先实现段落纯色 float paragraphCenterU = (paraIndex + 0.5f) * uvStepPerParagraph; // 取段落UV范围的中心 // 3. 遍历这个段落内的每一个字符(在TMP信息中的索引) for (int charIdxInPara = 0; charIdxInPara < paraLength; charIdxInPara++) { int actualCharIndex = paraStartCharIndex + charIdxInPara; // 确保不越界,并且该字符是有效的(非空格、非不可见字符等) if (actualCharIndex >= textInfo.characterCount) break; TMP_CharacterInfo charInfo = textInfo.characterInfo[actualCharIndex]; if (!charInfo.isVisible) continue; // 跳过空格和不可见字符 // 4. 获取这个字符的网格顶点索引,并修改其UV坐标 int materialIndex = charInfo.materialReferenceIndex; int vertexIndex = charInfo.vertexIndex; // 获取此字符对应的网格顶点数据 Vector2[] uvs0 = textInfo.meshInfo[materialIndex].uvs0; // uvs0通常存储主贴图UV // 一个字符由4个顶点(两个三角形)组成 for (int j = 0; j < 4; j++) { // 修改UV的U坐标,将其映射到我们为这个段落分配的中心U值。 // 注意:这里直接替换了原有的字体图集UV的U值,保留了V值。 // 这意味着字符形状依然来自字体图集,但颜色来自我们渐变贴图的特定位置。 Vector2 originalUV = uvs0[vertexIndex + j]; uvs0[vertexIndex + j] = new Vector2(paragraphCenterU, originalUV.y); } } } // 5. 将所有修改后的网格数据上传回渲染器 for (int i = 0; i < textInfo.meshInfo.Length; i++) { TMP_MeshInfo meshInfo = textInfo.meshInfo[i]; if (meshInfo.mesh != null) { meshInfo.mesh.uv = meshInfo.uvs0; // 仅更新UV,避免不必要的整体网格重建 m_TextComponent.UpdateGeometry(meshInfo.mesh, i); } } }

将脚本挂载到TMP_GradientText对象上,并将之前创建的渐变贴图拖拽到脚本的Gradient Texture字段。运行游戏,在TMP组件的Text输入框中输入多段文字(用回车换行),你应该能看到不同段落显示了渐变贴图上不同位置的颜色!

4.3 实现段落内部渐变

上面的代码让每个段落呈现单一颜色。如何实现一个段落内部也从左到右渐变呢?关键在于为段落内的每个字符计算一个基于其在段落中位置的、更精细的U值。

修改ApplyGradientToParagraphs方法中计算paragraphCenterU和遍历字符的部分:

// ... 前面段落检测代码不变 ... for (int paraIndex = 0; paraIndex < totalParagraphs; paraIndex++) { var (paraStartCharIndex, paraLength) = paragraphs[paraIndex]; // 计算这个段落在渐变贴图上占据的U范围 float paragraphUStart = paraIndex * uvStepPerParagraph; float paragraphUEnd = (paraIndex + 1) * uvStepPerParagraph; for (int charIdxInPara = 0; charIdxInPara < paraLength; charIdxInPara++) { int actualCharIndex = paraStartCharIndex + charIdxInPara; if (actualCharIndex >= textInfo.characterCount) break; TMP_CharacterInfo charInfo = textInfo.characterInfo[actualCharIndex]; if (!charInfo.isVisible) continue; // **关键修改:计算字符在段落内的水平位置比例** // 方法1:简单线性分布(不考虑字符宽度不均) // float t_inParagraph = (float)charIdxInPara / (paraLength - 1); // 方法2:基于字符中心点的x坐标在段落总宽度中的比例(更准确) // 我们需要先计算整个段落的总宽度(单位可能是像素或归一化坐标,这里需要根据TMP坐标空间调整) // 这里提供一个简化但有效的思路:使用字符在文本框中的索引进行线性插值。 // 对于大多数等宽字体或效果要求不极端的情况,方法1已经足够。 float t_inParagraph = (paraLength > 1) ? ((float)charIdxInPara / (paraLength - 1)) : 0.5f; // 根据比例,计算该字符在段落UV范围内的具体U值 float charU = Mathf.Lerp(paragraphUStart, paragraphUEnd, t_inParagraph); int materialIndex = charInfo.materialReferenceIndex; int vertexIndex = charInfo.vertexIndex; Vector2[] uvs0 = textInfo.meshInfo[materialIndex].uvs0; for (int j = 0; j < 4; j++) { Vector2 originalUV = uvs0[vertexIndex + j]; uvs0[vertexIndex + j] = new Vector2(charU, originalUV.y); } } } // ... 后面更新网格的代码不变 ...

实操心得:计算字符在段落内的位置比例(t_inParagraph)是效果自然与否的关键。简单的字符索引线性插值在字符宽度差异大时(如‘i’和‘W’)会导致颜色变化不匀。更精确的方法是获取字符几何中心的x坐标,相对于段落第一个和最后一个字符的中心x坐标进行归一化。这需要从charInfo.bottomLeftcharInfo.topRight等顶点位置信息中计算,逻辑更复杂但效果更佳。对于艺术性要求高的项目,建议实现更精确的版本。

5. 高级技巧与性能优化

基础功能实现后,我们还需要关注效果的美观性和运行效率。

5.1 处理富文本标签(如颜色、大小、粗体)

TextMeshPro的富文本标签(,等)会改变字符的材质索引(materialReferenceIndex)。我们的代码在遍历textInfo.meshInfo数组时,已经通过materialIndex区分了不同的材质子网格,因此天然支持富文本。但是,如果你在文本中使用了``标签,它会在顶点颜色上叠加一个颜色,这可能会干扰我们的渐变效果。有几种处理方式:

  1. 覆盖策略:在我们的Shader或材质中,确保Face ColorVertex Color的影响权重设置正确,让贴图颜色成为主导。通常将Face Color设为白色,并降低Vertex Color的权重(如果Shader支持)。
  2. 预处理文本:在应用渐变前,使用正则表达式移除文本中所有的颜色标签,或者将其替换为不影响我们算法的占位符。
    using System.Text.RegularExpressions; private string StripColorTags(string input) { // 移除类似<#FF00FF>或</color>的标签 return Regex.Replace(input, @"<color=[^>]*>|</color>", ""); } // 在设置m_TextComponent.text或ForceMeshUpdate前调用

5.2 动态渐变与动画

我们的渐变贴图是静态的,但我们可以动态修改它来实现动画效果。

  • 滚动渐变:每帧轻微偏移所有字符的UV坐标的U值,可以实现颜色在文字上流动的效果。注意在Update中频繁修改UV对性能有影响,需谨慎使用。

    public float scrollSpeed = 0.1f; private float currentUOffset = 0f; void Update() { if (animateGradient) { currentUOffset += Time.deltaTime * scrollSpeed; currentUOffset %= 1.0f; // 循环 ApplyGradientWithOffset(currentUOffset); // 需要实现一个带全局偏移量的应用方法 } }

    ApplyGradientWithOffset中,计算每个字符的最终U值时加上currentUOffset即可。

  • 动态生成贴图:如前所述,可以在运行时根据游戏状态(如玩家血量、能量值)生成从A色到B色的渐变贴图,然后重新赋值给材质并调用ApplyGradientToParagraphs

5.3 性能优化要点

  1. 避免每帧调用ForceMeshUpdateForceMeshUpdate()和遍历所有字符修改UV是相对昂贵的操作。务必只在文本内容真正改变时触发(我们已通过TEXT_CHANGED_EVENT实现)。对于静态文本,只需在StartOnEnable时调用一次。
  2. 减少网格更新范围UpdateGeometry会更新整个子网格。如果只有少量字符变化(如打字机效果),可以尝试只更新受影响字符所在的网格块,但这实现起来非常复杂。对于大多数UI场景,全文更新是可以接受的。
  3. 对象池与复用:如果场景中存在大量需要此效果的动态文本(如聊天框、日志),考虑使用对象池复用TMPParagraphGradient组件,避免频繁的AddComponentDestroy
  4. 使用Job System/Burst Compiler(高级):对于极大量文本(如全文小说阅读器),字符遍历和UV计算可以尝试用C# Job System并行化,并使用Burst Compiler加速。但这需要对ECS/Job有深入了解,且TextMeshPro的数据结构不是线程安全的,需要先复制到NativeArray中处理,复杂度陡增。

6. 常见问题排查与调试技巧

在实际使用中,你可能会遇到一些奇怪的现象。这里记录了我踩过的坑和解决方法。

6.1 问题:文字显示为纯白色或黑色,没有渐变。

  • 排查步骤
    1. 检查材质贴图:首先确认脚本中gradientTexture是否已赋值,并且材质实例m_MaterialInstance是否成功获取并设置了贴图。可以在AwakeStart中打印m_MaterialInstance.mainTexture的名字来确认。
    2. 检查Face Color:确保材质Face Color为纯白(255,255,255,255)。如果它是灰色或黑色,会和贴图颜色相乘导致变暗。
    3. 检查Shader:确认使用的Shader是支持贴图的。TextMeshPro的SDF Shader默认使用_FaceTex作为附加贴图。如果你用的Shader不支持,颜色可能来自别处。可以尝试在材质面板直接拖拽贴图,看预览球是否有渐变。
    4. 检查UV修改代码:在ApplyGradientToParagraphs方法中,在修改UV的循环里添加Debug.Log,打印出计算出的charU值,看是否在预期的[0, 1]范围内。

6.2 问题:渐变颜色错乱,或者所有文字颜色一样。

  • 排查步骤
    1. 段落检测逻辑:检查paragraphs列表是否正确生成。在计算完段落后,打印出每个段落的(start, length),看是否和你的输入文本匹配。常见的错误是忽略了首段或尾段,或者把空格行也算进去了。
    2. UV计算逻辑:确认uvStepPerParagraph的计算是否正确(1.0f / totalParagraphs)。如果totalParagraphs为0或1,会导致除零错误或不正确的步长。
    3. 字符索引映射:确保actualCharIndex(在TMP字符信息数组中的索引)与paraStartCharIndex + charIdxInPara的计算是正确的。特别注意:TMP的textInfo.characterInfo数组包含所有字符,包括空格和富文本标签生成的不可见字符。我们的paragraphs是基于原始字符串索引的,而charInfo.isVisible可以帮助我们跳过不可见字符,但索引对应关系需要仔细处理。一个更稳健的方法是直接遍历textInfo.characterInfo数组,同时维护一个段落索引和段落内字符计数器。

6.3 问题:文字变得模糊或边缘有锯齿。

  • 原因与解决
    1. 贴图Filter Mode:检查渐变贴图的导入设置,Filter Mode设为Bilinear,避免Point模式导致的色块。
    2. 材质Shader参数:如果是SDF Shader,检查Gradient ScaleSharpness参数。不合适的值会影响字体边缘的平滑度。通常使用默认值即可,除非你大幅改变了字体渲染方式。
    3. UV精度:如果你为每个字符计算了唯一的、非常精细的U值,当贴图分辨率不高时,相邻字符采样的颜色可能差异极小,但在某些显卡或渲染设置下可能出现精度问题。确保渐变贴图有足够的分辨率(如512x1)。

6.4 调试工具:在Scene视图中可视化UV

为了直观地看到UV修改是否生效,可以编写一个简单的Editor脚本,在Scene视图绘制字符的UV信息。

#if UNITY_EDITOR using UnityEditor; [CustomEditor(typeof(TMPParagraphGradient))] public class TMPParagraphGradientEditor : Editor { void OnSceneGUI() { TMPParagraphGradient tg = target as TMPParagraphGradient; if (tg == null || !tg.isActiveAndEnabled) return; TMP_Text textComp = tg.GetComponent<TMP_Text>(); if (textComp == null) return; TMP_TextInfo info = textComp.textInfo; for (int i = 0; i < info.characterCount; i++) { TMP_CharacterInfo charInfo = info.characterInfo[i]; if (!charInfo.isVisible) continue; // 获取字符的世界空间中心点 Vector3 bottomLeft = charInfo.bottomLeft; Vector3 topRight = charInfo.topRight; Vector3 center = (bottomLeft + topRight) / 2; // 假设你有办法获取到计算后的UV值,这里只是示意 // 你可以将UV的U值转换为一个颜色,在Scene视图画出来 // Handles.Label(center, $"U: {calculatedU:F2}"); } } } #endif

实现这个方案后,我将其应用在了那个视觉小说项目里。当主角情绪激动时,程序根据情绪值生成一张从蓝到红的渐变贴图,并应用到台词文本上。效果出奇的好,颜色的流动与语音的节奏和台词的含义同步,极大地增强了表现力。最关键的是,这一切的性能开销微乎其微,完全满足移动端的要求。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询