Unity TMP可控角度多色渐变文字

Unity TMP可控角度多色渐变文字

前言

遇到一个需要可以控制角度和支持多色渐变文字的需求,记录一下。

在 UI 设计中,渐变色文字是一种常见且非常吸引眼球的视觉效果。

然而,Unity 的 TextMeshPro(简称 TMP)原生只支持单色或简单的顶点渐变。

如果我们想要实现 多种颜色、可控比例、可调整方向(斜率) 的整体渐变效果,就需要自己扩展一段脚本。

项目

首先我们需要一个基本的 Unity UI 场景。

版本推荐:Unity 2021 LTS 或更高版本,并导入 TextMeshPro(默认已内置)。

场景布置

创建TextMeshPro - Text的文本

代码编写

计算文本所有顶点的二维坐标投影,并根据自定义颜色点(ColorStop)的分布与角度插值出最终颜色。

cs 复制代码
using UnityEngine;
using TMPro;

[ExecuteAlways]
[RequireComponent(typeof(TextMeshProUGUI))]
public class TMP_TextGradientVisualizer : MonoBehaviour
{
    [Header("渐变控制")]
    public Gradient gradient = new Gradient();   // 使用Unity自带的Gradient编辑器
    [Range(0f, 360f)]
    public float gradientAngle = 0f;             // 渐变方向(角度控制)

    private TextMeshProUGUI _tmp;
    private bool _needsUpdate = false;

    void Awake()
    {
        _tmp = GetComponent<TextMeshProUGUI>();
    }

    void OnEnable()
    {
        if (_tmp == null) _tmp = GetComponent<TextMeshProUGUI>();
        TMPro_EventManager.TEXT_CHANGED_EVENT.Add(OnTextChanged);
        ApplyGradient();
    }

    void OnDisable()
    {
        TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(OnTextChanged);
    }

    void LateUpdate()
    {
        if (_needsUpdate)
        {
            ApplyGradient();
            _needsUpdate = false;
        }
    }

    void OnTextChanged(Object obj)
    {
        if (obj == _tmp)
            _needsUpdate = true;
    }

    public void ApplyGradient()
    {
        if (_tmp == null || gradient == null)
            return;

        _tmp.ForceMeshUpdate(true, true);
        TMP_TextInfo textInfo = _tmp.textInfo;
        if (textInfo == null || textInfo.characterCount == 0) return;

        // 找出整体边界
        Vector2 min = new Vector2(float.MaxValue, float.MaxValue);
        Vector2 max = new Vector2(float.MinValue, float.MinValue);
        for (int i = 0; i < textInfo.characterCount; i++)
        {
            TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
            if (!charInfo.isVisible) continue;
            Vector3 bl = charInfo.bottomLeft;
            Vector3 tr = charInfo.topRight;
            min.x = Mathf.Min(min.x, bl.x);
            min.y = Mathf.Min(min.y, bl.y);
            max.x = Mathf.Max(max.x, tr.x);
            max.y = Mathf.Max(max.y, tr.y);
        }
        if (min.x == float.MaxValue) return;

        // 根据角度计算方向
        float rad = gradientAngle * Mathf.Deg2Rad;
        Vector2 dir = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)).normalized;

        // 计算投影范围
        float minProj = float.MaxValue, maxProj = float.MinValue;
        Vector2[] corners = new Vector2[]
        {
            new Vector2(min.x, min.y),
            new Vector2(max.x, min.y),
            new Vector2(min.x, max.y),
            new Vector2(max.x, max.y)
        };
        foreach (var c in corners)
        {
            float proj = Vector2.Dot(c, dir);
            minProj = Mathf.Min(minProj, proj);
            maxProj = Mathf.Max(maxProj, proj);
        }

        // 遍历所有网格顶点并设置颜色
        for (int i = 0; i < textInfo.meshInfo.Length; i++)
        {
            TMP_MeshInfo meshInfo = textInfo.meshInfo[i];
            if (meshInfo.vertices == null) continue;

            Color32[] colors = meshInfo.colors32;
            Vector3[] vertices = meshInfo.vertices;

            for (int j = 0; j < vertices.Length; j++)
            {
                Vector2 v = vertices[j];
                float proj = Vector2.Dot(v, dir);
                float t = Mathf.InverseLerp(minProj, maxProj, proj);
                colors[j] = gradient.Evaluate(t);
            }
        }

        _tmp.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);
    }

#if UNITY_EDITOR
    void OnValidate()
    {
        if (_tmp == null) _tmp = GetComponent<TextMeshProUGUI>();
        if (!Application.isPlaying)
        {
            UnityEditor.EditorApplication.delayCall += () =>
            {
                if (this != null && _tmp != null)
                    ApplyGradient();
            };
        }
    }
#endif
}

原理解析

  1. 顶点投影计算

    每个 TMP 顶点都是二维平面坐标,我们根据指定角度构造一个单位方向向量 dir。

    然后用点积计算该顶点在该方向上的投影值,从而确定它处于渐变线的哪一段。

  2. 归一化插值

    通过 Mathf.InverseLerp(minProj, maxProj, proj) 把投影值映射到 0~1。

  3. 颜色插值

    按顺序遍历 colorStops,在相邻两个节点之间进行线性插值 Color.Lerp(a, b, t)。

  4. 全局更新网格颜色

    修改 meshInfo.colors32 后,用 _tmp.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32) 更新到屏幕。

添加并设置脚本

挂载脚本并设置

  • Gradient Angle:控制渐变的方向(0°水平,90°垂直,其它值为斜向)
  • Color Stops:控制渐变颜色节点。


总结

之后可以添加GradientPreset资源,统一 UI 风格。

相关推荐
天人合一peng2 分钟前
unity 生成标记根据背景色标记变色
unity·游戏引擎
天人合一peng4 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安4 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU24 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法4 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎
迪捷软件5 小时前
显控系统虚拟仿真的工程化路径
游戏引擎·cocos2d
凡情9 小时前
android隐私合规检测
android·unity
小贺儿开发9 小时前
Unity3D 本地 Stable Diffusion 文生图效果演示
人工智能·unity·stable diffusion·文生图·ai绘画·本地化
Swift社区10 小时前
传统游戏引擎 vs 鸿蒙 System 架构
架构·游戏引擎·harmonyos
mxwin1 天前
Unity Shader 半透明物体为什么不能写入深度缓冲?
unity·游戏引擎·shader