其实这个需求用TextMeshPro一分钟就能搞定,可TMP对中文动态字体的支持并不完美。针对即时输出的随机化文本,只能扩展Text来实现了。
基本思路
获得输出文本的每个字的位置和宽高数据。这个通过字体渲染时的顶点数据可以拿到。
算法整合每行的最大高度
整合所有行的bounds信息
根据bounds大小来绘制Image
Code
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class TextBg : BaseMeshEffect
{
public class CharQuad
{
public UIVertex TopLeft { get; set; }
public UIVertex TopRight { get; set; }
public UIVertex BottomRight { get; set; }
public UIVertex BottomLeft { get; set; }
public float Left { get; set; }
public float Right { get; set; }
public float Top { get; set; }
public float Bottom { get; set; }
public void Calculate()
{
Left = TopLeft.position.x;
Right = TopRight.position.x;
Top = TopLeft.position.y;
Bottom = BottomLeft.position.y;
}
}
public override void ModifyMesh(VertexHelper vh)
{
if (!IsActive())
{
return;
}
int count = vh.currentVertCount;
if (count <= 0)
{
return;
}
List<UIVertex> vertices = new List<UIVertex>();
for (int i = 0; i < count; i++)
{
UIVertex vertex = new UIVertex();
vh.PopulateUIVertex(ref vertex, i);
vertex.color = Color.red;
vertices.Add(vertex);
//vh.SetUIVertex(vertex, i);
}
int charlen = count / 4;
#if UNITY_EDITOR
Debug.LogWarningFormat("text vertices count = {0} charlen = {1}", count, charlen);
#endif
CharQuad[] charquads = new CharQuad[charlen];
//根据顶点拓扑顺序
for (int i = 0; i < charlen; i++)
{
CharQuad cquad = new CharQuad();
cquad.TopLeft = vertices[i * 4];
cquad.TopRight = vertices[i * 4 + 1];
cquad.BottomRight = vertices[i * 4 + 2];
cquad.BottomLeft = vertices[i * 4 + 3];
cquad.Calculate();
charquads[i] = cquad;
}
//计算text行字符
//如果字符换行了
//1.n+1字符的top<=n字符的bottom
List<CharQuad[]> cquadslist = new List<CharQuad[]>();
if (charlen == 1)
{
cquadslist = new List<CharQuad[]>();
cquadslist.Add(charquads);
}
else
{
List<CharQuad> cqlist = new List<CharQuad>();
cqlist.Add(charquads[0]);
for (int i = 1; i < charquads.Length; i++)
{
CharQuad n0char = charquads[i - 1];
CharQuad n1char = charquads[i];
if (n1char.Top <= n0char.Bottom)
{
cquadslist.Add(cqlist.ToArray());
cqlist.Clear();
}
cqlist.Add(charquads[i]);
}
if (cqlist.Count > 0)
{
cquadslist.Add(cqlist.ToArray());
}
}
//计算bounds
//计算text每一行的bound
/*Vector4[] */bounds = new Vector4[cquadslist.Count];
for (int i = 0; i < cquadslist.Count; i++)
{
CharQuad[] cquads = cquadslist[i];
float left = float.MaxValue;
float right = float.MinValue;
float top = float.MinValue;
float bottom = float.MaxValue;
for (int k = 0; k < cquads.Length; k++)
{
CharQuad cq = cquads[k];
if (left > cq.Left)
{
left = cq.Left;
}
if (right < cq.Right)
{
right = cq.Right;
}
if (top < cq.Top)
{
top = cq.Top;
}
if (bottom > cq.Bottom)
{
bottom = cq.Bottom;
}
}
#if UNITY_EDITOR
Debug.LogWarningFormat("text mesh line = {0} charlen = {1} left = {2} right = {3} top = {4} bottom = {5}", i, cquads.Length, left, right, top, bottom);
#endif
bounds[i] = new Vector4(left, right, top, bottom);
}
//bounds参数以text组件中心点为原点,所以参数存在负值,需要处理成正值
//以text组件左下角为原点,即可完成正值化
{
float left = float.MaxValue;
float right = float.MinValue;
float top = float.MinValue;
float bottom = float.MaxValue;
for (int i = 0; i < bounds.Length; i++)
{
Vector4 bound = bounds[i];
if (left > bound.x)
{
left = bound.x;
}
if (right < bound.y)
{
right = bound.y;
}
if (top < bound.z)
{
top = bound.z;
}
if (bottom > bound.w)
{
bottom = bound.w;
}
}
float width = right - left + 1;
float height = top - bottom + 1;
#if UNITY_EDITOR
Debug.LogWarningFormat("text texture left = {0} right = {1} top = {2} bottom = {3} width = {4} height = {5}", left, right, top, bottom, width, height);
#endif
StartCoroutine(delayDrawImage());
}
}
Vector4[] bounds;
private List<Image> _lines = new List<Image>();
void CreateBg()
{
for (int i = 0; i < transform.childCount; i++)
{
GameObject.DestroyImmediate(transform.GetChild(i).gameObject);
Debug.LogWarning("DESOTRY: " + i);
}
Vector4[] list = bounds;
_lines.Clear();
Debug.LogWarning("CreateUnderLines==========================list.length:" + list.Length);
for (int i = 0; i < list.Length; i++)
{
//初始化
GameObject obj = new GameObject();
obj.transform.SetParent(transform, false);
obj.name = "underline" + i;
_lines.Add(obj.AddComponent<Image>());
_lines[i].rectTransform.pivot = new Vector2(0, 1);
_lines[i].rectTransform.anchorMin = new Vector2(0, 1);
_lines[i].rectTransform.anchorMax = new Vector2(0, 1);
//颜色和大小
float fWidth = Mathf.Abs((list[i].y - list[i].x));
float fHeight = Mathf.Abs((list[i].w - list[i].z));
var tex = new Texture2D((int)fWidth, (int)fHeight, TextureFormat.ARGB32, false);
Color[] colors = tex.GetPixels();
for (int j = 0; j < colors.Length; j++)
colors[j] = new Color(1, 0, 0, 0.2f);
tex.SetPixels(colors);
tex.Apply();
_lines[i].sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), Vector2.zero);
_lines[i].SetNativeSize();
_lines[i].rectTransform.sizeDelta = new Vector2(fWidth, fHeight);
_lines[i].rectTransform.anchorMin = new Vector2(0.5f, 0.5f);
_lines[i].rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
//坐标
float x = list[i].x;
_lines[i].rectTransform.anchoredPosition = new Vector2(x, list[i].z);
}
}
IEnumerator delayDrawImage()
{
yield return null;
CreateBg();
}
}
备注
Image的删除会报错,有空再优化了