本文章用于原生组件 Text 的扩展 TextRainbow,对于新版TextMeshPro不适用。
一、效果预览图:
默认:
随机:
循环:
二、原理
通过强制刷新顶点数据,来修改颜色。
通过Unity中自带的 BaseMeshEffect 抽象类,可以直接修改UI元素的网格,从而达到比如阴影,描边,UV顶点颜色等视觉效果。
新建一个脚本继承 BaseMeshEffect 抽象类,通过 ModifyMesh 方法来自定义想要的效果。 ModifyMesh 方法中自带参数类型 VertexHelper,通过参数类型中提供的 GetUIVertexStream、PopulateUIVertex 和 SetUIVertex 方法来获取当前网格的顶点数据并重新设置回去。
彩虹颜色的方式有很多种,这里推荐使用渐变 Gradient 去做比较方便。Gradient 的颜色设置和获取在代码中已有展示,没啥好说的。
对于颜色的显示三种方式,循环模式是通过Update方法驱动渐变颜色索引实现的,并且添加是否忽略引擎的TimeScale影响。
三、代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;
[RequireComponent(typeof(Text))]
public class TextRainbow : BaseMeshEffect
{
public enum VerticesColorType
{
None,
Random,
Loop
}
[SerializeField] protected Text target;
/// <summary>
/// 彩虹条
/// </summary>
public Gradient gradient = new();
/// <summary>
/// 彩虹条颜色数量
/// </summary>
private int ColorCount => gradient.colorKeys.Length;
/// <summary>
/// 当前索引下标 0 ~ gradientColors
/// </summary>
private int _index = 0;
/// <summary>
/// 进度,0 ~ 1
/// </summary>
private float _timeProcess = 0;
private bool _openRainbow = false;
/// <summary>
/// 存在彩虹文本
/// </summary>
private bool _haveRainbow = false;
private readonly List<UIVertex> _vertices = new();
/// <summary>
/// 滚动速度
/// </summary>
public float ScrollSpeed = 3f;
/// <summary>
/// 打开彩虹
/// </summary>
public bool OpenRainbow
{
get => _openRainbow;
set
{
if (_openRainbow == value)
{
return;
}
_openRainbow = value;
UpdateIndex();
HaveRainbowCondition();
target.SetVerticesDirty();
}
}
/// <summary>
/// 彩虹颜色类型
/// </summary>
private VerticesColorType _colorType = VerticesColorType.None;
public VerticesColorType ColorType
{
get => _colorType;
set
{
if (_colorType == value)
{
return;
}
_colorType = value;
UpdateIndex();
if (_openRainbow)
{
target.SetVerticesDirty();
}
}
}
/// <summary>
/// 忽略时间缩放
/// </summary>
public bool unScaledTime = false;
public string Text
{
get => target.text;
set => SetText(value);
}
protected override void Awake()
{
_index = 0;
if (!target)
{
target = GetComponent<Text>();
}
}
private void Update()
{
if (!OpenRainbow)
{
return;
}
if (!_haveRainbow)
{
return;
}
if (_colorType != VerticesColorType.Loop) return;
_timeProcess += (unScaledTime ? Time.unscaledDeltaTime : Time.deltaTime) * ScrollSpeed;
if (!(_timeProcess >= 1)) return;
_timeProcess -= 1;
_index++;
target.SetVerticesDirty();
if (_index >= ColorCount)
{
_index = 0;
}
}
#if UNITY_EDITOR
protected override void Reset()
{
base.Reset();
target = GetComponent<Text>();
}
#endif
public override void ModifyMesh(VertexHelper vh)
{
if (!_haveRainbow)
{
return;
}
if (!IsActive() || vh.currentIndexCount == 0)
{
return;
}
_vertices.Clear();
vh.GetUIVertexStream(_vertices);
UIVertex uiVertex = new();
for (var i = 0; i < vh.currentVertCount; ++i)
{
vh.PopulateUIVertex(ref uiVertex, i);
uiVertex.color = CalcColor(i);
vh.SetUIVertex(uiVertex, i);
}
}
private Color CalcColor(int index)
{
if (!OpenRainbow)
{
return target.color;
}
index /= 4;
index += _index;
if (index >= ColorCount)
{
index %= ColorCount;
}
return gradient.Evaluate(gradient.colorKeys[index].time);
}
private void SetText(string text)
{
if (string.IsNullOrEmpty(text))
{
_haveRainbow = false;
target.text = string.Empty;
return;
}
_haveRainbow = OpenRainbow;
target.text = text;
}
private void HaveRainbowCondition()
{
if (string.IsNullOrEmpty(Text))
{
_haveRainbow = false;
return;
}
_haveRainbow = OpenRainbow;
}
private void UpdateIndex()
{
switch (_colorType)
{
case VerticesColorType.Random:
_index = Random.Range(0, ColorCount);
break;
case VerticesColorType.None:
case VerticesColorType.Loop:
default:
_index = 0;
break;
}
}
}
面板扩展:
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(TextRainbow), true)]
[CanEditMultipleObjects]
public class TextRainbowEditor : Editor
{
private const int NumberOfColors = 7;
private TextRainbow Target => (TextRainbow)target;
private SerializedProperty _component;
private SerializedProperty _unScaledTime;
protected void OnEnable()
{
_component = serializedObject.FindProperty("target");
_unScaledTime = serializedObject.FindProperty("unScaledTime");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.PropertyField(_component);
GUI.enabled = true;
EditorGUI.BeginChangeCheck();
#region Gradient
EditorGUILayout.BeginHorizontal();
Target.gradient = EditorGUILayout.GradientField("Gradient", Target.gradient);
// 预设彩虹渐变
if (GUILayout.Button("Rainbow", GUILayout.Width(80)))
{
DrawRainbowGradient();
}
EditorGUILayout.EndHorizontal();
#endregion
Target.ColorType = (TextRainbow.VerticesColorType)EditorGUILayout.EnumPopup("Color Type", Target.ColorType);
Target.ScrollSpeed = EditorGUILayout.Slider("Scroll Speed", Target.ScrollSpeed, 0, 10);
Target.OpenRainbow = EditorGUILayout.Toggle("Open Rainbow", Target.OpenRainbow);
EditorGUILayout.PropertyField(_unScaledTime);
EditorGUILayout.LabelField("Text");
Target.Text = EditorGUILayout.TextArea(Target.Text, GUILayout.MinHeight(EditorGUIUtility.singleLineHeight * 3));
if (EditorGUI.EndChangeCheck())
{
EditorUtility.SetDirty(Target);
}
serializedObject.ApplyModifiedProperties();
}
// 预设彩虹渐变
private void DrawRainbowGradient()
{
var colorKeys = new GradientColorKey[NumberOfColors];
for (var i = 0; i < NumberOfColors; i++)
{
float dividend = NumberOfColors - 1;
var index = i;
if (i == 1)
{
dividend = 12f;
}
else if (i > 1)
{
index = i - 1;
}
colorKeys[i].color = Color.HSVToRGB(index / dividend, 1f, 1f);
colorKeys[i].time = (float)i / (NumberOfColors - 1);
}
Target.gradient.colorKeys = colorKeys;
Target.gradient.alphaKeys = new[]
{
new GradientAlphaKey(1, 0),
new GradientAlphaKey(1, 1)
};
}
}
写出来后稍加整理就发出来了,如果有更优解(原生组件扩展)欢迎指教。