今天遇到一个需要给UIText添加删除线的功能,以前博主一般是制作一个同样宽度的Image,盖在Text上,但是这次需要动态换行,所以就重写了Text的功能,具体代码如下。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class DeleteLineText : Text
{
public float distance = 4f;
public float lineHeight = 4f;
public Color underLineColor;
private bool useUnderLineColor = true;
private List<UIVertex> textVertArr = new List<UIVertex>();
private int startIndex = 0;
private int endIndex = 0;
protected override void Start()
{
base.Start();
underLineColor = Color.red;
lineHeight = 5.0f;
distance = -15.0f;
}
protected override void OnPopulateMesh(VertexHelper toFill)
{
base.OnPopulateMesh(toFill); //渲染text字体的顶点
toFill.GetUIVertexStream(textVertArr); //toFill创建vertex流 vertex数组的长度是文字长度的6倍 因为一个文字有六个顶点
if (textVertArr.Count <= 0) return;
SetUnderLine(toFill);
}
private void SetUnderLine(VertexHelper vh)
{
if(m_Text.Length <= 0)
{
return;
}
endIndex = m_Text.Length - 1;
List<int> lineStartList = new List<int>();
List<int> lineEndList = new List<int>();
SavePosByCharacter(ref lineStartList, ref lineEndList);
if (lineStartList.Count == 1)
{
// 单个字
Vector2 startPos = new Vector2(textVertArr[lineStartList[0]].position.x, textVertArr[lineStartList[0]].position.y);
Vector2 endPos = new Vector2(textVertArr[lineEndList[0]].position.x, textVertArr[lineEndList[0]].position.y);
DrawLine(vh, startPos, endPos);
}
else
{
// 多个字
for (int i = 0; i < lineStartList.Count; i++)
{
Vector2 startPos = new Vector2(textVertArr[lineStartList[i]].position.x, textVertArr[lineStartList[i]].position.y);
Vector2 endPos = new Vector2(textVertArr[lineEndList[i]].position.x, textVertArr[lineEndList[i]].position.y);
DrawLine(vh, startPos, endPos);
}
}
}
private void SavePosByCharacter(ref List<int> lineStartList, ref List<int> lineEndList)
{
// 对于每一个字处理
for (int i = startIndex; i <= endIndex; i++)
{
// 得到最底下的两个点
int minPos = i * 6;
int maxPos = i * 6;
for (int j = i * 6; j < (i + 1) * 6; j++)
{
if (textVertArr[j].position.x < textVertArr[minPos].position.x)
{
minPos = j;
}
else if (Mathf.Approximately(textVertArr[minPos].position.x, textVertArr[j].position.x)&& textVertArr[j].position.y < textVertArr[minPos].position.y)
{
minPos = j;
}
if (textVertArr[j].position.x > textVertArr[maxPos].position.x)
{
maxPos = j;
}
else if (Mathf.Approximately(textVertArr[maxPos].position.x, textVertArr[j].position.x) && textVertArr[j].position.y < textVertArr[maxPos].position.y)
{
maxPos = j;
}
}
lineStartList.Add(minPos);
lineEndList.Add(maxPos);
}
}
private void DrawLine(VertexHelper vh, Vector2 startPos, Vector2 endPos)
{
Vector2 extents = rectTransform.rect.size;
var setting = GetGenerationSettings(extents);
TextGenerator underlineText = new TextGenerator();
underlineText.Populate("-", setting);
if (distance > fontSize && lineHeight > font.fontSize)
{
Debug.LogError("UnderLine property is Error! please make Certain property can't greater than fontSize.");
return;
}
IList<UIVertex> lineVer = underlineText.verts;/*new UIVertex[4];*///"_"的的顶点数组
Vector3[] pos = new Vector3[4];
pos[0] = startPos + new Vector2(-8, -distance);
pos[3] = startPos + new Vector2(-8, -(distance + lineHeight));
pos[2] = endPos + new Vector2(8, -(distance + lineHeight));
pos[1] = endPos + new Vector2(8, -distance);
Color tempColor = useUnderLineColor ? underLineColor : color;
UIVertex[] tempVerts = new UIVertex[4];
for (int i = 0; i < 4; i++)
{
tempVerts[i] = lineVer[i];
tempVerts[i].color = tempColor;
tempVerts[i].position = pos[i];
tempVerts[i].uv0 = lineVer[i].uv0;
tempVerts[i].uv1 = lineVer[i].uv1;
tempVerts[i].uv2 = lineVer[i].uv2;
tempVerts[i].uv3 = lineVer[i].uv3;
}
vh.AddUIVertexQuad(tempVerts);
}
}
大体思路是这样的,首先我们通过toFill.GetUIVertexStream(textVertArr)获取到了所有文字的顶点,这个数组的数量应该是文字数量的6倍,因为每个文字都由两个三角形也就是六个顶点构成。
然后通过比较,找到每个文字底部最小的位置和最大的位置,并把它们存储到一个数组中。接下来,我们重写Text的OnPopulateMesh函数,在这个函数中,为Text增加顶点。增加的逻辑是对于每一个文字,都在它底部的最小位置和最大位置之间增加四个点,组成一个长方形,也就是我们需要的下划线。
有的博主会把同一行的下划线变成四个点,博主试过这个做法,性能比较好,但是效果有一些问题,在下划线的两端,会有一个明显的渐变效果,所以我选择了对每个文字制作一个下划线,但是这样导致在改变文字的时候FPS的下降,算是各有利弊吧。
最后我们把下划线的位置上移,也就得到了我们的删除线。感觉这个算法还是有些问题,未来有更好的方法会继续更新的。