最近无意中找到的,UGUI表情系统解决方案
作者通过重写Text的OnPopulateMesh,重新生成网格,把代表表情的编号"[XXX]"替换为%%,把%%在转为表情网格,最终通过UIVertex.uv1计算传值给shader绘制出来.
我试了下Emoji没什么问题,后来想加个超链接功能,在原有基础上做了修改,计算%%的占位老是出错,于是我把%%直接换成了一个汉字,这样就方便多了.
/// <summary>
/// 表情使用汉字为占位符
/// </summary>
private readonly static char s_SpecialCharacters = '圞';
如果用其他汉字,文本里出现的概率会很大,所以这里用了特殊的汉字.
在Text的OnPopulateMesh中计算数据,需要继承 Text, IPointerClickHandler
详细逻辑如下
protected override void OnPopulateMesh(VertexHelper toFill)
{
Debug.Log("OnPopulateMesh");
string orignText = m_Text;
if (font == null)
return;
if (s_EmojiIndex == null)
{
s_EmojiIndex = new Dictionary<string, EmojiInfo>();
// load emoji data, and you can overwrite this segment code base on your project.
TextAsset emojiContent = Resources.Load<TextAsset>("emoji");
string[] lines = emojiContent.text.Split('\n');
for (int i = 1; i < lines.Length; i++)
{
if (!string.IsNullOrEmpty(lines[i]))
{
string[] strs = lines[i].Split('\t');
EmojiInfo info;
info.x = float.Parse(strs[3]);
info.y = float.Parse(strs[4]);
info.size = float.Parse(strs[5]);
s_EmojiIndex.Add(strs[1], info);
}
}
}
// 支持富文本
if (supportRichText)
{
m_TempEmoji.Clear();
m_ShowEmojiData.Clear();
s_TextBuilder.Length = 0;
int lastSubIndex = 0;
//data里面已经吧[0]换成了{0},这里把所有表情代码替换成特殊汉字,存到容器里
foreach (Match match in Regex.Matches(text, "\\{[a-z0-9A-Z]+\\}"))
{
EmojiInfo info;
if (s_EmojiIndex.TryGetValue(match.Value, out info))
{
if (lastSubIndex != match.Index)
{
s_TextBuilder.Append(text.Substring(lastSubIndex, match.Index - lastSubIndex));
}
m_TempEmoji.Add(info);
s_TextBuilder.Append(s_SpecialCharacters);
lastSubIndex = match.Index + match.Length;
}
}
if (lastSubIndex != text.Length - 3)
{
s_TextBuilder.Append(text.Substring(lastSubIndex));
}
//在把代表超链接标签转为<color={0}></color>
m_EmojiText = GetOutputText(s_TextBuilder.ToString());
s_TextBuilder.Length = 0;
s_TextBuilder.Append(m_EmojiText);
int index = 0;
//记录下表情所在下标,最终渲染需要用到
for (int i = 0; i < s_TextBuilder.Length; i++)
{
if (s_TextBuilder[i].Equals(s_SpecialCharacters) && index < m_TempEmoji.Count)
{
m_ShowEmojiData.Add(i, m_TempEmoji[index++]);
}
}
}
else
{
m_EmojiText = text;
}
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
cachedTextGenerator.Populate(m_EmojiText, settings);
Rect inputRect = rectTransform.rect;
// 获取text的alignment anchor
Vector2 textAnchorPivot = GetTextAnchorPivot(alignment);
Vector2 refPoint = Vector2.zero;
refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y);
// 确定要偏移网格的像素。
Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;
// 将偏移应用于顶点
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
//最后4节总是一条新的线
int vertCount = verts.Count - 4;
toFill.Clear();
if (roundingOffset != Vector2.zero)
{
for (int i = 0; i < vertCount; ++i)
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
for (int i = 0; i < vertCount; ++i)
{
EmojiInfo info;
int index = i / 4;
if (m_ShowEmojiData.TryGetValue(index, out info))
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
{
//可动态设置offset值对表情大小进行控制
float offset = 4;
m_TempVerts[0].position = m_TempVerts[0].position + new Vector3(-offset, offset);
m_TempVerts[1].position = m_TempVerts[1].position + new Vector3(offset, offset);
m_TempVerts[2].position = m_TempVerts[2].position + new Vector3(offset, -offset);
m_TempVerts[3].position = m_TempVerts[3].position + new Vector3(-offset, -offset);
m_TempVerts[0].uv1 = new Vector2(info.x, info.y + info.size);
m_TempVerts[1].uv1 = new Vector2(info.x + info.size, info.y + info.size);
m_TempVerts[2].uv1 = new Vector2(info.x + info.size, info.y);
m_TempVerts[3].uv1 = new Vector2(info.x, info.y);
toFill.AddUIVertexQuad(m_TempVerts);
}
}
else
{
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
if (tempVertsIndex == 3)
toFill.AddUIVertexQuad(m_TempVerts);
}
}
}
m_Text = orignText;
// 超链接包围框
foreach (var hrefInfo in m_HrefInfos)
{
hrefInfo.boxes.Clear();
if (hrefInfo.startIndex >= toFill.currentVertCount)
{
continue;
}
// 将超链接里面的文本顶点索引坐标加入到包围框
toFill.PopulateUIVertex(ref m_TempBoxVert, hrefInfo.startIndex);
var pos = m_TempBoxVert.position;
var bounds = new Bounds(pos, Vector3.zero);
for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++)
{
if (i >= toFill.currentVertCount)
{
break;
}
toFill.PopulateUIVertex(ref m_TempBoxVert, i);
pos = m_TempBoxVert.position;
if (pos.x < bounds.min.x) // 换行重新添加包围框
{
hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
bounds = new Bounds(pos, Vector3.zero);
}
else
{
bounds.Encapsulate(pos); // 扩展包围框
}
}
hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
}
m_DisableFontTextureRebuiltCallback = false;
}
/// <summary>
/// 点击事件检测是否点击到超链接文本
/// </summary>
/// <param name="eventData"></param>
public void OnPointerClick(PointerEventData eventData)
{
Vector2 lp;
RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out lp);
foreach (var hrefInfo in m_HrefInfos)
{
var boxes = hrefInfo.boxes;
for (var i = 0; i < boxes.Count; ++i)
{
if (boxes[i].Contains(lp))
{
m_OnHrefClick.Invoke(hrefInfo.name);
return;
}
}
}
}
/// <summary>
/// 获取超链接解析后的最后输出文本
/// </summary>
/// <returns></returns>
protected string GetOutputText(string text)
{
s_TextBuilder.Length = 0;
m_HrefInfos.Clear();
int indexText = 0;
foreach (Match match in s_HrefRegex.Matches(text))
{
s_TextBuilder.Append(text.Substring(indexText, match.Index - indexText));
Debug.Log(m_HyperlinkColor12);
s_TextBuilder.AppendFormat("<color={0}>", m_HyperlinkColor12); // 超链接颜色
var group = match.Groups[1];
HrefInfo hrefInfo = new HrefInfo
{
startIndex = s_TextBuilder.Length * 4, // 超链接里的文本起始顶点索引
endIndex = (s_TextBuilder.Length + match.Groups[2].Length - 1) * 4 + 3,
name = group.Value
};
m_HrefInfos.Add(hrefInfo);
s_TextBuilder.Append(match.Groups[2].Value);
s_TextBuilder.Append("</color>");
indexText = match.Index + match.Length;
}
s_TextBuilder.Append(text.Substring(indexText, text.Length - indexText));
return s_TextBuilder.ToString();
}
超链接标签格式:<a href=name>[show]</a>
文本内容为:
{0}这是一个支持表情系统{1}的EmojiText{2},运行时支持动态表情。
{3}<a href=12>[前往]</a>{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}口
<a href=qw>[前往]</a>
最终效果为:2个Text也只会占用一个batches