• unity ugui text 超链接和下划线,支持部分富文本格式


    unity版本:2021.3.6f1
    局限性:
    1.测试发现不能使用 size 富文本标签,
    2.同一文本不能设置不同颜色的超链接文本
    其它:代码中注释掉使用innerTextColor的地方,可以使用富文本设置超链接颜色, 但是下划线是文本本身颜色

    项目需要用到该功能, 搜索和参考了很多文章,要么不支持富文本,要不没有下划线,要么是错误的,修修改改后满足我的需求,代码如下

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.EventSystems;
    
    namespace MyTool.Tools
    {
        /// 
        /// 文本控件支持超链接、下划线
        /// 
        public class HyperlinkText : Text, IPointerClickHandler
        {
            public Action<string> onHyperlinkClick;
    
            /// 超链接信息类
            private class HyperlinkInfo
            {
                public int startIndex;
                public int endIndex;
                public string name;
                public readonly List<Rect> boxes = new List<Rect>();
                public List<int> linefeedIndexList = new List<int>();
            }
    
            /// 解析完最终的文本
            private string m_OutputText;
    
            /// 超链接信息列表
            private readonly List<HyperlinkInfo> m_HrefInfos = new List<HyperlinkInfo>();
    
            /// 文本构造器
            protected StringBuilder s_TextBuilder = new StringBuilder();
    
            [Tooltip("超链接文本颜色")]
            [SerializeField] private Color32 innerTextColor = new Color32(36, 64, 180, 255);
    
            /// 超链接正则
            private static readonly Regex s_HrefRegex = new Regex(@"\n\s]+)>(.*?)()", RegexOptions.Singleline);
    
            // ugui富文本标签
            // 格式1:  
            private static readonly string[] _uguiSymbols1 = { "b", "i" };
            // 格式2: 
            private static readonly string[] _uguiSymbols2 = { "color", "size" };
    
            public string GetHyperlinkInfo { get { return text; } }
    
            public override void SetVerticesDirty()
            {
                base.SetVerticesDirty();
    
                text = GetHyperlinkInfo;
                m_OutputText = GetOutputText(text);
            }
    
            protected override void OnPopulateMesh(VertexHelper toFill)
            {
                var orignText = m_Text;
                m_Text = m_OutputText;
                base.OnPopulateMesh(toFill);
                m_Text = orignText;
                UIVertex vert = new UIVertex();
    
                // 处理超链接包围框
                foreach (var hrefInfo in m_HrefInfos)
                {
                    hrefInfo.boxes.Clear();
                    hrefInfo.linefeedIndexList.Clear();
                    if (hrefInfo.startIndex >= toFill.currentVertCount)
                        continue;
    
                    // 将超链接里面的文本顶点索引坐标加入到包围框
                    toFill.PopulateUIVertex(ref vert, hrefInfo.startIndex);
    
                    var pos = vert.position;
                    var bounds = new Bounds(pos, Vector3.zero);
                    hrefInfo.linefeedIndexList.Add(hrefInfo.startIndex);
                    for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i++)
                    {
                        if (i >= toFill.currentVertCount)
                            break;
    
                        toFill.PopulateUIVertex(ref vert, i);
                        vert.color = innerTextColor;
                        toFill.SetUIVertex(vert, i);
    
                        pos = vert.position;
    
                        bool needEncapsulate = true;
    
                        if (i > 4 && (i - hrefInfo.startIndex) % 4 == 0)
                        {
                            UIVertex lastV = new UIVertex();
                            toFill.PopulateUIVertex(ref lastV, i - 4);
                            var lastPos = lastV.position;
    
                            if (pos.x < lastPos.x && pos.y < lastPos.y) // 换行重新添加包围框
                            {
                                hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
                                hrefInfo.linefeedIndexList.Add(i);
                                bounds = new Bounds(pos, Vector3.zero);
                                needEncapsulate = false;
                            }
                        }
                        if (needEncapsulate)
                        {
                            bounds.Encapsulate(pos); // 扩展包围框
                        }
                    }
                    hrefInfo.boxes.Add(new Rect(bounds.min, bounds.size));
                }
    
                //一个字一个字的划 效率差 而且字与字之间容易有接缝
                DrawUnderLine(toFill);
            }
    
            private void DrawUnderLine(VertexHelper vh)
            {
                UIVertex vert = new UIVertex();
                List<Vector3> startPosList = new List<Vector3>();
                List<Vector3> endPosList = new List<Vector3>();
                foreach (var hrefInfo in m_HrefInfos)
                {
                    if (hrefInfo.startIndex >= vh.currentVertCount) continue;
    
                    float minY = float.MaxValue;
                    for (int i = hrefInfo.startIndex, m = hrefInfo.endIndex; i < m; i += 4)
                    {
                        if (i >= vh.currentVertCount)
                            break;
    
                        if (hrefInfo.linefeedIndexList.Contains(i))
                        {
                            for (int j = 0; j < startPosList.Count; j++)
                            {
                                MeshUnderLine(vh, new Vector2(startPosList[j].x, minY), new Vector2(endPosList[j].x, minY));
                            }
                            startPosList.Clear();
                            endPosList.Clear();
                        }
    
                        vh.PopulateUIVertex(ref vert, i + 3);
                        startPosList.Add(vert.position);
                        vh.PopulateUIVertex(ref vert, i + 2);
                        endPosList.Add(vert.position);
    
                        if (vert.position.y < minY)
                        {
                            minY = vert.position.y;
                        }
                    }
    
                    for (int j = 0; j < startPosList.Count; j++)
                    {
                        MeshUnderLine(vh, new Vector2(startPosList[j].x, minY), new Vector2(endPosList[j].x, minY));
                    }
                    startPosList.Clear();
                    endPosList.Clear();
                }
            }
    
            private void MeshUnderLine(VertexHelper vh, Vector2 startPos, Vector2 endPos)
            {
                Vector2 extents = rectTransform.rect.size;
                var setting = GetGenerationSettings(extents);
    
                TextGenerator underlineText = new TextGenerator();
                underlineText.Populate("—", setting);
    
                IList<UIVertex> lineVer = underlineText.verts;/*new UIVertex[4];*///"_"的的顶点数组
    
                Vector3[] pos = new Vector3[4];
                pos[0] = startPos + new Vector2(-1f, 0);
                pos[3] = startPos + new Vector2(-1f, 4f);
                pos[2] = endPos + new Vector2(1f, 4f);
                pos[1] = endPos + new Vector2(1f, 0);
    
    
                UIVertex[] tempVerts = new UIVertex[4];
                for (int i = 0; i < 4; i++)
                {
                    tempVerts[i] = lineVer[i];
                    tempVerts[i].color = innerTextColor;
                    tempVerts[i].position = pos[i];
                }
    
                vh.AddUIVertexQuad(tempVerts);
            }
    
            /// 
            /// 获取超链接解析后的最后输出文本
            /// 
            /// 
            protected virtual string GetOutputText(string outputText)
            {
                s_TextBuilder.Length = 0;
                m_HrefInfos.Clear();
                var indexText = 0;
                int count = 0;
                foreach (Match match in s_HrefRegex.Matches(outputText))
                {
                    string appendStr = outputText.Substring(indexText, match.Index - indexText);
    
                    s_TextBuilder.Append(appendStr);
    
                    //空格和回车没有顶点渲染,所以要去掉
                    count += appendStr.Length - appendStr.Replace(" ", "").Replace("\n", "").Length;
                    //去掉富文本标签的长度
                    for (int i = 0; i < _uguiSymbols1.Length; i++)
                    {
                        count += appendStr.Length - appendStr.Replace($"<{_uguiSymbols1[i]}>", "").Replace($"{_uguiSymbols1[i]}>", "").Length;
                    }
                    for (int i = 0; i < _uguiSymbols2.Length; i++)
                    {
                        string pattern = $"<{_uguiSymbols2[i]}=(.*?)>";
                        count += appendStr.Length - Regex.Replace(appendStr, pattern, "").Length;
                        count += appendStr.Length - appendStr.Replace($"{_uguiSymbols2[i]}>", "").Length;
                    }
    
                    int startIndex = (s_TextBuilder.Length - count) * 4;
                    var group = match.Groups[1];
                    var hrefInfo = new HyperlinkInfo
                    {
                        startIndex = startIndex, // 超链接里的文本起始顶点索引
                        endIndex = startIndex + (match.Groups[2].Length * 4),
                        name = group.Value
                    };
                    m_HrefInfos.Add(hrefInfo);
    
                    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 = Vector2.zero;
                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))
                        {
                            if (onHyperlinkClick != null)
                                onHyperlinkClick.Invoke(hrefInfo.name);
    
                            return;
                        }
                    }
                }
            }
    
    
    
    #if UNITY_EDITOR
    		//需延迟调用该方法
            private void AddVisibleBound()
            {
                int index = 0;
    
                foreach (var hrefInfo in m_HrefInfos)
                {
                    Color color = new Color(UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), UnityEngine.Random.Range(0f, 1f), 0.2f);
                    index++;
                    foreach (Rect rect in hrefInfo.boxes)
                    {
                        GameObject gameObject = new GameObject();
                        gameObject.name = string.Format("GOBoundBox[{0}]", hrefInfo.name);
                        gameObject.transform.SetParent(this.gameObject.transform, false);
    
                        RectTransform rectTransform = gameObject.AddComponent<RectTransform>();
                        rectTransform.sizeDelta = rect.size;
                        rectTransform.localPosition = new Vector3(rect.position.x + rect.size.x / 2, rect.position.y + rect.size.y / 2, 0);
    
                        Image image = gameObject.AddComponent<Image>();
                        image.color = color;
                        image.raycastTarget = false;
                    }
                }
            }
    #endif
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295

    编辑器扩展
    在这里插入图片描述
    代码

    using UnityEditor;
    using UnityEngine;
    
    namespace MyTool
    {
        [CanEditMultipleObjects]
        [CustomEditor(typeof(Tools.HyperlinkText), true)]
        public class HyperlinkTextEditor : UnityEditor.UI.TextEditor
        {
            SerializedProperty _innerTextColor;
    
            protected override void OnEnable()
            {
                base.OnEnable();
                _innerTextColor = serializedObject.FindProperty("innerTextColor");
            }
    
            public override void OnInspectorGUI()
            {
                base.OnInspectorGUI();
    
                serializedObject.Update();
                EditorGUILayout.PropertyField(_innerTextColor, new GUIContent("Inner Text Color"));
                serializedObject.ApplyModifiedProperties();
                if (GUI.changed)
                {
                    EditorUtility.SetDirty(target);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Demo代码

    using MyTool.Tools;
    using System.Collections;
    using UnityEngine;
    
    public class Demo2 : MonoBehaviour
    {
        public HyperlinkText text;
        // Start is called before the first frame update
        void Start()
        {
            //设置点击回调
            text.onHyperlinkClick = OnClickText;
    
            StartCoroutine(AddVisibleBound());
        }
    
        IEnumerator AddVisibleBound()
        {
            yield return null;
            text.AddVisibleBound();
        }
    
        void OnClickText(string s)
        {
            if (s == "第一段第一句")
            {
                Debug.Log($"111---{s}");
            }
            else if (s == "第二段第一句")
            {
                Debug.Log($"222---{s}");
            }
            else
            {
                Debug.Log($"333---{s}");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    demo测试文本

    <color=#ffffff><b><size=36>背着手踱着。</size></b></color><color=ffffff><href=第一段第一句>路上只我一个人</href></color><size=45>这一片天地好像是我的;我也像超出了平常旳自己,到了另一世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫旳月下,什么都可以想,什么都可以不想,便觉是个自由的人。</size>白天里一定要做的事,一定要说的话,现在都可不理。<b><color=green>这是独处的妙处,我且受用这无边的荷香月色好了。</color></b>
    
    <href=第二段第一句>曲曲折折的荷塘上面</href>,弥望旳是田田的叶子。叶子出水很高,像亭亭旳舞女旳裙。层层的叶子中间,零星地点缀着些白花,有袅娜(niǎo,nuó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉()的流水,遮住了,不能见一些颜色;而叶子却更见风致了。
    
    <size=60>月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为</size>这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,<href=第三段>如梵婀(ē)(英语violin小提琴的译音)上奏着的名曲</href><color=red><i>荷塘的四面,远远近近,高高低低都是树,而杨柳最多。</i></color><href=第四段>这些树将一片荷塘重重围住</href>;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ui
    在这里插入图片描述
    效果
    在这里插入图片描述

  • 相关阅读:
    深度学习 opencv python 实现中国交通标志识别 计算机竞赛_1
    Python学习三(面向对象)
    python自动化之(django)(2)
    2022谷粒商城学习笔记(九)分组关联属性相关功能
    Kafka 消息队列 ( 四 ) 复杂应用
    MySQL的二进制安装
    SpringBoot项目将Nacos作为配置中心与注册中心,微服务启动失败
    linux安装mysql8
    通过Power Platform自定义D365CE业务需求 - 1. Microsoft Power Apps 简介
    有人会画pcb吗,那个缝合孔用proteus怎么弄
  • 原文地址:https://blog.csdn.net/weixin_44238530/article/details/133822439