Unity 图文混排和超链接

最近无意中找到的,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

发布了2 篇原创文章 · 获赞 4 · 访问量 534

猜你喜欢

转载自blog.csdn.net/flj135792468/article/details/104006380