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
{/// <summary>/// 文本控件支持超链接、下划线/// </summary>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(@"<href=([^>\n\s]+)>(.*?)(</href>)", RegexOptions.Singleline);// ugui富文本标签// 格式1:<b></b> <i></i>private static readonly string[] _uguiSymbols1 = { "b", "i" };// 格式2:<color=#ffffff></color> <color=red></color>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);}/// <summary>/// 获取超链接解析后的最后输出文本/// </summary>/// <returns></returns>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();}/// <summary>/// 点击事件检测是否点击到超链接文本/// </summary>/// <param name="eventData"></param>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}
}
编辑器扩展
代码
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);}}}
}
Demo代码
using MyTool.Tools;
using System.Collections;
using UnityEngine;public class Demo2 : MonoBehaviour
{public HyperlinkText text;// Start is called before the first frame updatevoid 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}");}}
}
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ó)地开着旳,有羞涩地打着朵儿旳;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉(mò)的流水,遮住了,不能见一些颜色;而叶子却更见风致了。<size=60>月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样;又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为</size>这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,<href=第三段>如梵婀(ē)玲(英语violin小提琴的译音)上奏着的名曲</href>。<color=red><i>荷塘的四面,远远近近,高高低低都是树,而杨柳最多。</i></color><href=第四段>这些树将一片荷塘重重围住</href>;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。
ui
效果