The solution of hyperlink click realized by Text of UGUI in Unity

Unity implements hyperlink click

  • Function introduction:
    • C# script:
    • Instructions
    • Screenshot of Demo project
    • Demo address:

Function introduction:

1. Multiple clicks of different character areas can be realized in the same Text;
2. Adapted to Chinese, English, Korean, Japanese, Arabic, etc., more languages to be tested;

C# script:

/********************************************** *********************
** File name: UIText_Link.cs
** Copyright: (C)
** Created by: Summer
** Date: 2022/3/2
** Description: hyperlink text
*
    "<a i=id>%s</a>"
***************************************************** *******************/
using System;
using UnityEngine;
using System. Text;
using UnityEngine.UI;
using System. Collections;
using UnityEngine. EventSystems;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Text_Link : Text, IPointerClickHandler
{<!-- -->
    protected override void Awake()
    {<!-- -->
        Set_TextLinkFuncCB((string i) =>
        {<!-- -->
            Debug.Log($"Clicked: {<!-- -->i}");
        });
    }
    /// <summary>
    /// After parsing the final text
    /// </summary>
    private string m_OutputText;

    /// <summary>
    /// List of hyperlink information
    /// </summary>
    private readonly List<HrefInfo_> m_HrefInfos = new List<HrefInfo_>();

    /// <summary>
    /// Text constructor
    /// </summary>
    protected static readonly StringBuilder s_TextBuilder = new StringBuilder();

    /// <summary>
    /// Hyperlink regular
    /// </summary>
    private static readonly Regex s_HrefRegex =
        new Regex(@"<a i=([^>\
\s] + )>(.*?)(</a>)", RegexOptions.Singleline);

    //
    private static readonly Regex s_VertexFilter = new Regex(@"(|[ \
\r\t] + )", RegexOptions.Singleline);

    VertexHelper_toFill = null;
    /// <summary>
    /// Whether to use hyperlinks, the default is not False
    /// </summary>
    bool bool_IsLink = false;

    private Action<string> linkFunc_Cb = null;

    private RectTransform rect_Parent;
    private RectTransform Rect_Parent
    {<!-- -->
        get
        {<!-- -->
            if (rect_Parent == null)
            {<!-- -->
                Transform trans = this.transform.parent != null ? this.transform.parent.transform : this.transform;
                rect_Parent = trans. GetComponent<RectTransform>();
            }

            return rect_Parent;
        }
    }

    //Set the click callback event of the text hyperlink
    public void Set_TextLinkFuncCB(Action<string> linkFunc_Cb)
    {<!-- -->
        bool_IsLink = true;
        if (this. linkFunc_Cb != null)
        {<!-- -->
            this.linkFunc_Cb = null;
        }
        this.linkFunc_Cb = linkFunc_Cb;
        OnPopulateMesh(_toFill);
    }

    //Number of character vertices
    const int perCharVerCount = 4;

    /// <summary>
    /// Text constructor
    /// </summary>
    protected static readonly StringBuilder textRebuild = new StringBuilder();

    protected override void OnPopulateMesh(VertexHelper toFill)
    {<!-- -->
        if (toFill == null)
        {<!-- -->
            return;
        }
        _toFill = toFill;
        //This is not executed in the TODO editor state, which is convenient for debugging and seeing the effect
        if (!bool_IsLink)
        {<!-- -->
            m_Text = GetOutputText_Nomal(text);
            base.OnPopulateMesh(toFill);
            return;
        }

        var orignText = m_Text;
        m_OutputText = GetOutputText_Init(text);
        m_Text = m_OutputText;
        text = m_OutputText;
        base.OnPopulateMesh(toFill);
        m_Text = orignText;
        GetOutputText(text, toFill. currentVertCount);

        UIVertex vert = new UIVertex();

        // handle hyperlink bounding box
        foreach (var hrefInfo in m_HrefInfos)
        {<!-- -->
            hrefInfo.boxes.Clear();
            if (hrefInfo.startIndex >= toFill.currentVertCount)
            {<!-- -->
                continue;
            }

            // Add the text vertex index coordinates in the hyperlink to the bounding box
            toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex);
            var pos = vert. position;
            var bounds = new Bounds(pos, Vector3.zero);
            Vector3 previousPos = Vector3. zero;
            for (int i = hrefInfo. startIndex, m = hrefInfo. endIndex; i < m; i ++ )
            {<!-- -->
                if (i >= toFill. currentVertCount)
                {<!-- -->
                    break;
                }

                toFill.PopulateUIVertex(ref vert, i);
                pos = vert. position;
                if ((i - hrefInfo. startIndex) % 4 == 1)
                {<!-- -->
                    previousPos = pos;
                }
                if (previousPos != Vector3.zero & amp; & amp; (i - hrefInfo.startIndex) % 4 == 0 & amp; & amp; pos.x < previousPos.x) // Newline and re-add bounding box
                {<!-- -->
                    hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
                    bounds = new Bounds(pos, Vector3.zero);
                }
                else
                {<!-- -->
                    bounds.Encapsulate(pos); // Expand the bounding box
                }
            }
            hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
        }


        if (this. gameObject. activeInHierarchy)
        {<!-- -->
            StartCoroutine(RefrehLayout());
        }
    }

    IEnumerator RefreshLayout()
    {<!-- -->
        yield return new WaitForEndOfFrame();
        LayoutRebuilder.ForceRebuildLayoutImmediate(Rect_Parent);
    }

    //Initialize the hyperlink text to obtain the fixed-point number of the final result
    string GetOutputText_Init(string outputText)
    {<!-- -->
        s_TextBuilder. Length = 0;
        m_HrefInfos. Clear();
        var indexText = 0;
        foreach (Match match in s_HrefRegex. Matches(outputText))
        {<!-- -->
            s_TextBuilder.Append(outputText.Substring(indexText, match.Index - indexText));
            s_TextBuilder.Append("<i><b><color=#f49037>"); // hyperlink color

            s_TextBuilder.Append(match.Groups[2].Value);
            s_TextBuilder.Append("</color></b></i>");
            indexText = match.Index + match.Length;
        }
        s_TextBuilder.Append(outputText.Substring(indexText, outputText.Length - indexText));
        return s_TextBuilder. ToString();
    }

    /// <summary>
    /// Get the final output text after hyperlink parsing
    /// </summary>
    /// <returns></returns>
    string GetOutputText(string outputText, int currentVertCount)
    {<!-- -->
        s_TextBuilder. Length = 0;
        m_HrefInfos. Clear();
        var indexText = 0;
        int vertCount = Regex.Replace(Regex.Replace(outputText.ToString(), @"\s", ""), @"<(.*?)>", "").Length * 4;
        int vercCount_Offset_Start = 0;
        int vercCount_Offset_End = 0;
        bool isLineCup = false;
        if (currentVertCount > vertCount)
        {<!-- -->
            isLineCup = true;
            vercCount_Offset_Start = 80;
            vercCount_Offset_End = 88;
        }
        foreach (Match match in s_HrefRegex. Matches(outputText))
        {<!-- -->
            s_TextBuilder.Append(outputText.Substring(indexText, match.Index - indexText));
            int offset_Len = 0;
            if (isLineCup)
            {<!-- -->
                offset_Len = (s_TextBuilder.Length - Regex.Replace(s_TextBuilder.ToString(), @"<(.*?)>", "").Length) * 4;
            }

            s_TextBuilder.Append("<i><b><color=#f49037>"); // hyperlink color

            var str = Regex.Replace(s_TextBuilder.ToString(), @"\s", "");
            var group = match. Groups[1];
            var hrefInfo = new HrefInfo_
            {<!-- -->
                startIndex = Regex.Replace(str, @"<(.*?)>", "").Length * 4 + vercCount_Offset_Start + offset_Len, // The starting vertex index of the text in the hyperlink
                endIndex = (Regex.Replace(str, @"<(.*?)>", "").Length +
                Regex.Replace(Regex.Replace(match.Groups[2].ToString(), @"\s", "")
                , @"<(.*?)>", "").Length - 1) * 4 + 3 + vercCount_Offset_End + offset_Len,
                name = group. Value
            };
            m_HrefInfos.Add(hrefInfo);
            //Debug.Log($"Vertex information, start: {hrefInfo.startIndex}, end: {hrefInfo.endIndex}");

            s_TextBuilder.Append(match.Groups[2].Value);
            s_TextBuilder.Append("</color></b></i>");
            indexText = match.Index + match.Length;
        }

        s_TextBuilder.Append(outputText.Substring(indexText, outputText.Length - indexText));
        return s_TextBuilder. ToString();
    }

    //Obtain and remove hyperlinks and keep normal text to ensure that the text of the hyperlink label in the configuration can also be used normally in other places, only the text in the form of hyperlinks will be given if the hyperlink initialization is called
    string GetOutputText_Nomal(string outputText)
    {<!-- -->
        s_TextBuilder. Length = 0;
        m_HrefInfos. Clear();
        var indexText = 0;
        MatchCollection matches = s_HrefRegex. Matches(outputText);
        if (matches. Count <= 0)
        {<!-- -->
            return outputText;
        }
        foreach (Match match in matches)
        {<!-- -->
            s_TextBuilder.Append(outputText.Substring(indexText, match.Index - indexText));
            s_TextBuilder.Append(match.Groups[2].Value);
            indexText = match.Index + match.Length;
        }
        s_TextBuilder.Append(outputText.Substring(indexText, outputText.Length - indexText));
        return s_TextBuilder. ToString();
    }

    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))
                {<!-- -->
                    //Debug.Log("Skill hyperlink clicked: " + hrefInfo.name);
                    linkFunc_Cb?.Invoke(hrefInfo.name);
                    return;
                }
            }
        }
    }
}
/// <summary>
/// Hyperlink information class
/// </summary>
class HrefInfo_
{<!-- -->
    public int startIndex;

    public int endIndex;

    public string name;

    public readonly List<Rect> boxes = new List<Rect>();
}

How to use

1. Use rich text in the text box, see screenshot

2. To make the hyperlink function of the text take effect, just call:

 Set_TextLinkFuncCB((string i) =>
        {<!-- -->
            Debug.Log($"Clicked: {<!-- -->i}");
        });

The flexibility of setting the callback is relatively high, and you can expand it yourself

Screenshot in the Demo project

Demo address:

https://github.com/Panda0000000000000/TextLink.git