using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System; using System.Linq; using System.Text; public class RichText : Text, IPointerClickHandler { /// /// 最终显示文本 /// private string m_OutputText; private string m_RichText = string.Empty; public Action OnClick; #region 对外参数 [SerializeField] private float m_ImgDeltay = 0; public float ImgDeltay { get { return m_ImgDeltay; } set { m_ImgDeltay = value; SetVerticesDirty(); } } [SerializeField] private float m_unlineDeltay = 0; public float UnlineDeltay { get { return m_unlineDeltay; } set { m_unlineDeltay = value; } } [SerializeField] private float m_unlineHeight = 1; public float UnlineHeight { get { return m_unlineHeight; } set { m_unlineHeight = value; } } [SerializeField] private float m_faceSize = 32; public float FaceSize { get { return m_faceSize; } set { m_faceSize = value; } } [SerializeField] private bool m_unline = true; public bool Unline { get { return m_unline; } set { m_unline = value; } } [SerializeField] private bool m_HrefClick = true; public bool HrefClick { get { return m_HrefClick; } set { m_HrefClick = value; } } [SerializeField] private string m_EnableDisplay; public string enableDisplay { get { return m_EnableDisplay; } set { m_EnableDisplay = value; } } [SerializeField] private bool m_Language = false; public bool language { get { return m_Language; } set { m_Language = value; } } [SerializeField] private bool m_AutoNewLine = false; public bool AutoNewLine { get { return m_AutoNewLine; } set { m_AutoNewLine = value; SetRichTextDirty(); } } [SerializeField] private bool m_LockImgSize = false; public bool LockImgSize { get { return m_LockImgSize; } set { m_LockImgSize = value; } } [SerializeField] private bool m_ModifyImgSize = false; public bool ModifyImgSiez { get { return m_ModifyImgSize; } set { m_ModifyImgSize = value; } } [SerializeField] private float m_ModifyImgWidth = 0; public float ModifyImgWidth { get { return m_ModifyImgWidth; } set { m_ModifyImgWidth = value; } } [SerializeField] private float m_ModifyImgHeight = 0; public float ModifyImgHeight { get { return m_ModifyImgHeight; } set { m_ModifyImgHeight = value; } } [SerializeField] private ColorType m_ColorType = ColorType.Dark; public ColorType colorType { get { return m_ColorType; } set { m_ColorType = value; } } public enum ColorType { Dark, Bright, } #endregion protected override void Awake() { #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabType(this) == UnityEditor.PrefabType.Prefab) { return; } #endif unline = transform.GetComponentInChildren(); if (unline == null) { GameObject obj = UILoader.LoadPrefab("TextUnline") as GameObject; obj = Instantiate(obj); obj.transform.SetParent(transform); obj.transform.localScale = Vector3.one; unline = obj.GetComponent(); unline.raycastTarget = false; } } protected override void OnEnable() { base.OnEnable(); #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabType(this) == UnityEditor.PrefabType.Prefab) { return; } #endif if (language && !string.IsNullOrEmpty(enableDisplay) && Application.isPlaying && ConfigInitiator.done) { text = Language.Get(enableDisplay); } } #region 绘制 private UIVertex vert = new UIVertex(); protected override void OnPopulateMesh(VertexHelper toFill) { if (font == null) return; toFill.Clear(); base.OnPopulateMesh(toFill); CalcBounds(toFill); #region 修改图片位置 if (m_ImgList == null) return; for (int i = 0; i < m_ImgList.Count; i++) { int _imgEnd = m_ImgList[i].end; RectTransform rt = m_ImgPool[i].rectTransform; Vector2 _imgSize = rt.sizeDelta; if (_imgEnd < toFill.currentIndexCount) { try { toFill.PopulateUIVertex(ref vert, _imgEnd); rt.localPosition = new Vector2(vert.position.x + _imgSize.x / 2, vert.position.y + _imgSize.y / 2 - m_ImgDeltay); toFill.PopulateUIVertex(ref vert, _imgEnd - 3); Vector3 pos = vert.position; for (int j = _imgEnd, k = _imgEnd - 3; j >= k; j--) { toFill.PopulateUIVertex(ref vert, j); pos.y = Mathf.Min(pos.y, vert.position.y - ImgDeltay); vert.position = pos; toFill.SetUIVertex(vert, j); } } catch (System.Exception) { continue; } } } #endregion } public override void SetVerticesDirty() { base.SetVerticesDirty(); #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabType(this) == UnityEditor.PrefabType.Prefab) { return; } #endif //m_OutputText = GetOutputText(m_RichText); GenerateImg(); } #endregion #region 解析 private string GetOutputText(string _text) { string result = _text; result = GetExtenalData(result); result = GetTaskInfo(result); result = RichTextMgr.Inst.Analysis(result, out m_ImgList, out m_HrefList, this); return result; } #endregion #region 图片 private List m_ImgList; private List m_ImgPool = new List(); private void GenerateImg() { if (m_ImgList == null) return; m_ImgPool.Clear(); GetComponentsInChildren(true, m_ImgPool); for (int i = 0; i < m_ImgList.Count; i++) { RichTextMgr.ImgInfo imgInfo = m_ImgList[i]; Image img; if (i >= m_ImgPool.Count) { img = CreateImg(); m_ImgPool.Add(img); } else { img = m_ImgPool[i]; } img.SetActive(true); img.raycastTarget = HrefClick; img.sprite = imgInfo.sprite; Vector2 _imgSize = new Vector2(imgInfo.width, imgInfo.height); img.rectTransform.sizeDelta = _imgSize; UIFrame frame = img.GetComponent(); if (imgInfo.IsFace) { if (frame == null) frame = img.gameObject.AddComponent(); img.raycastTarget = false; frame.ResetFrame(imgInfo.spriteName); frame.enabled = true; } else { if (frame != null) frame.enabled = false; } } for (int i = m_ImgPool.Count - 1; i >= m_ImgList.Count; i--) { m_ImgPool[i].SetActive(false); } } private Image CreateImg() { DefaultControls.Resources res = new DefaultControls.Resources(); GameObject go = DefaultControls.CreateImage(res); go.layer = gameObject.layer; RectTransform rt = go.transform as RectTransform; if (rt != null) { rt.SetParent(rectTransform); rt.localPosition = Vector3.zero; rt.localRotation = Quaternion.identity; rt.localScale = Vector3.one; } Image img = go.GetComponent(); return img; } #endregion #region 下划线||超链接 [SerializeField] private TextUnline unline; private List m_HrefList; private Dictionary m_UnlineBottomDict = new Dictionary(); private float GetUnlineHeight(int _line) { foreach (var _key in m_UnlineBottomDict.Keys) { if (_line <= _key) { return m_UnlineBottomDict[_key]; } } return m_UnlineBottomDict.Values.Last(); } public string GetHrefMessage(string _key) { if (m_HrefList != null) { for (int i = 0; i < m_HrefList.Count; i++) { if (m_HrefList[i].mSplits.ContainsKey(_key)) { return m_HrefList[i].mSplits[_key]; } } } return string.Empty; } private void CalcBounds(VertexHelper toFill) { if (unline != null) unline.ClearVert(); if (m_HrefList == null) return; float minBottom = 0; if (!Unline) { return; } m_UnlineBottomDict.Clear(); int _line = 0; bool _note = false; foreach (RichTextMgr.HrefInfo hrefInfo in m_HrefList) { CalcUnline(toFill, hrefInfo); if (!hrefInfo.unline) { continue; } hrefInfo.lines.Clear(); for (int i = 0; i < hrefInfo.boxs.Count; i++) { Vector3 start = hrefInfo.boxs[i].position; if (minBottom == 0 && !_note) { minBottom = start.y; _note = true; } if (Math.Abs(minBottom - start.y) >= fontSize / 2) { m_UnlineBottomDict.Add(_line, minBottom); minBottom = start.y; _line++; } else { minBottom = Mathf.Min(start.y, minBottom); } hrefInfo.lines.Add(_line); } } m_UnlineBottomDict.Add(_line, minBottom); foreach (RichTextMgr.HrefInfo hrefInfo in m_HrefList) { if (!hrefInfo.unline) { continue; } for (int i = 0; i < hrefInfo.boxs.Count; i++) { Vector3 start = hrefInfo.boxs[i].position; var _unlineline = i < hrefInfo.lines.Count ? hrefInfo.lines[i] : 0; start.y = GetUnlineHeight(_unlineline); Vector3 end = start; end.x += hrefInfo.boxs[i].width; unline.SetUIVertex(start, end, -UnlineDeltay, m_unlineHeight, hrefInfo.unlineColor); } } } private void CalcUnline(VertexHelper toFill, RichTextMgr.HrefInfo hrefInfo) { hrefInfo.boxs.Clear(); if (hrefInfo.start >= toFill.currentIndexCount) return; try { toFill.PopulateUIVertex(ref vert, hrefInfo.start); } catch (System.Exception) { return; } Vector3 pos = vert.position; var realStart = hrefInfo.start; ///-----为了过滤颜色导致多次添加包围盒 while (realStart <= hrefInfo.end) { try { toFill.PopulateUIVertex(ref vert, realStart); if (pos != vert.position) { if (realStart <= hrefInfo.start + 1) { realStart = hrefInfo.start; } else { pos = vert.position; } break; } } catch (Exception) { return; } realStart++; } Bounds bounds = new Bounds(pos, Vector3.zero); int _Cnt = 0; for (int i = realStart, k = hrefInfo.end; i <= k; i++) { if (i >= toFill.currentIndexCount) break; try { toFill.PopulateUIVertex(ref vert, i); } catch (System.Exception) { break; } pos = vert.position; if (pos.x - 0.1 <= bounds.min.x && _Cnt > 3) { hrefInfo.boxs.Add(new Rect(bounds.min, bounds.size)); bounds = new Bounds(pos, Vector3.zero); _Cnt = 0; } bounds.Encapsulate(pos); _Cnt++; } hrefInfo.boxs.Add(new Rect(bounds.min, bounds.size)); } #endregion #region 自适应 public override float preferredWidth { get { if (font == null && Application.isPlaying) { font = FontUtility.preferred; } var settings = GetGenerationSettings(Vector2.zero); return cachedTextGeneratorForLayout.GetPreferredWidth(m_OutputText, settings) / pixelsPerUnit; } } public override float preferredHeight { get { if (font == null && Application.isPlaying) { font = FontUtility.preferred; } var settings = GetGenerationSettings(new Vector2(rectTransform.rect.size.x, 0.0f)); float _height = cachedTextGeneratorForLayout.GetPreferredHeight(m_OutputText, settings) / pixelsPerUnit; return _height; } } #endregion #region 外部数据 private static readonly string FORMAT_PATTERN = @"\%s([0-9]+)"; private ArrayList extenalData = new ArrayList(); public void SetExtenalData(params object[] msg) { extenalData.Clear(); if (msg != null && msg.Length > 0) { extenalData.AddRange(msg); } } public void SetExtenalData(ICollection collection) { extenalData.Clear(); if (collection != null && collection.Count > 0) { extenalData.AddRange(collection); collection = null; } } private string GetExtenalData(string val) { textBuilder.Length = 0; int index = 0; if (Regex.IsMatch(val, FORMAT_PATTERN)) { foreach (Match match in Regex.Matches(val, FORMAT_PATTERN)) { textBuilder.Append(val.Substring(index, match.Index - index)); int infoIndex = 0; int.TryParse(match.Groups[1].Value, out infoIndex); if (extenalData != null && infoIndex < extenalData.Count) { textBuilder.Append(extenalData[infoIndex]); } else { textBuilder.Append(0); } index = match.Index + match.Length; } textBuilder.Append(val.Substring(index, val.Length - index)); return textBuilder.ToString(); } else { return val; } } #region 任务数据 private Dictionary extenalDataDic = null; private const string Info_Pattern = "{([a-zA-Z0-9_]+)}"; public void SetReplaceInfo(Dictionary _infoDic) { extenalDataDic = _infoDic; SetRichTextDirty(); } private string GetTaskInfo(string val) { textBuilder.Length = 0; int index = 0; if (Regex.IsMatch(val, Info_Pattern)) { foreach (Match match in Regex.Matches(val, Info_Pattern)) { textBuilder.Append(val.Substring(index, match.Index - index)); if (extenalDataDic != null && extenalDataDic.ContainsKey(match.Groups[1].Value)) { textBuilder.Append(extenalDataDic[match.Groups[1].Value]); } else { textBuilder.Append(0); } index = match.Index + match.Length; } textBuilder.Append(val.Substring(index, val.Length - index)); return textBuilder.ToString(); } else { return val; } } #endregion #endregion #region 执行事件 public void OnPointerClick(PointerEventData eventData) { if (HrefClick) { Vector2 lp; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out lp); if (m_HrefList != null) { foreach (var hrefInfo in m_HrefList) { List boxs = hrefInfo.boxs; for (int i = 0; i < boxs.Count; i++) { if (boxs[i].Contains(lp)) { hrefInfo.Execute(); return; } } } } } if (OnClick != null) OnClick(); } public void OnImgClick() { } public void ExcuteHref(int eventIndex = 0, int hrefIndex = 0) { if (m_HrefList == null || m_HrefList.Count <= 0 || hrefIndex < 0) return; if (hrefIndex < m_HrefList.Count) { m_HrefList[hrefIndex].Execute(eventIndex); } } #endregion #region 自适应2 /// /// 牺牲了主动换行 /// private static StringBuilder textBuilder = new StringBuilder(); private Dictionary matchDics = new Dictionary(); private bool IsModifySize(int _index,out int _size) { _size = 0; int _end = 0; int _before = -1; foreach (var _key in matchDics.Keys) { if (_index < _key) { var _match = matchDics[_key]; if (WordAnalysis.Size_Start_Regex.IsMatch(_match.Value)) { break; } else if (WordAnalysis.Size_End_Regex.IsMatch(_match.Value)) { _end = _key; break; } } } if (_end != 0) { foreach (var _key in matchDics.Keys) { if (_key < _end) { var _match = matchDics[_key]; if (WordAnalysis.Size_Start_Regex.IsMatch(_match.Value)) { _before = _key; } } else { break; } } } if (_before != -1) { _size = int.Parse(matchDics[_before].Groups[1].Value); return true; } return false; } public void SetFitterSize() { string fitterText = m_OutputText; fitterText = fitterText.Replace("\n", ""); float width = 0; textBuilder.Length = 0; if (null == font && Application.isPlaying) { font = FontUtility.preferred; } var settings = GetGenerationSettings(Vector2.zero); float cache = 0; string part = string.Empty; float ratio = GetResolutionRatio(); matchDics.Clear(); foreach (Match match in ImgAnalysis.Unity_Img_Regex.Matches(fitterText)) { if (!matchDics.ContainsKey(match.Index)) { matchDics.Add(match.Index, match); } } foreach (Match match in WordAnalysis.Color_Start_Regex.Matches(fitterText)) { if (!matchDics.ContainsKey(match.Index)) { matchDics.Add(match.Index, match); } } foreach (Match match in WordAnalysis.Color_End_Regex.Matches(fitterText)) { if (!matchDics.ContainsKey(match.Index)) { matchDics.Add(match.Index, match); } } bool _modifySize = false; foreach (Match match in WordAnalysis.Size_Start_Regex.Matches(fitterText)) { if (!matchDics.ContainsKey(match.Index)) { _modifySize = true; matchDics.Add(match.Index, match); } } foreach (Match match in WordAnalysis.Size_End_Regex.Matches(fitterText)) { if (!matchDics.ContainsKey(match.Index)) { matchDics.Add(match.Index, match); } } for (int i = 0; i < fitterText.Length; i++) { if (matchDics.ContainsKey(i)) { Match match = matchDics[i]; if (ImgAnalysis.Unity_Img_Regex.IsMatch(match.Value)) { width += match.Length; } if (WordAnalysis.Color_Start_Regex.IsMatch(match.Value)) { textBuilder.Append(match.Value); } else if (WordAnalysis.Color_End_Regex.IsMatch(match.Value)) { textBuilder.Append(match.Value); } else if (WordAnalysis.Size_Start_Regex.IsMatch(match.Value)) { textBuilder.Append(match.Value); } else if (WordAnalysis.Size_End_Regex.IsMatch(match.Value)) { textBuilder.Append(match.Value); } else { cache = cachedTextGeneratorForLayout.GetPreferredWidth(match.Value, settings) * ratio; if (width + cache > (rectTransform.rect.width - 5)) { CacluHrefAndImgIndex(Mathf.Max(0, i - 1)); textBuilder.Append("\n"); width = cache; } else { width += cache; } textBuilder.Append(match.Value); } i += (match.Length - 1); } else { var _size = 0; var _cacheFontSize = fontSize; if (_modifySize && IsModifySize(i, out _size)) { fontSize = _size; settings = GetGenerationSettings(Vector2.zero); cache = cachedTextGeneratorForLayout.GetPreferredWidth(fitterText[i].ToString(), settings) * ratio; fontSize = _cacheFontSize; settings = GetGenerationSettings(Vector2.zero); } else { cache = cachedTextGeneratorForLayout.GetPreferredWidth(fitterText[i].ToString(), settings) * ratio; } if (width + cache > (rectTransform.rect.width - 5)) { CacluHrefAndImgIndex(Mathf.Max(0, i - 1)); textBuilder.Append("\n"); width = cache; } else { width += cache; } textBuilder.Append(fitterText[i]); } } CacluHrefAndImgIndex(); m_OutputText = textBuilder.ToString(); } private void CacluHrefAndImgIndex(int index) { for (int i = 0; i < m_ImgList.Count; i++) { if (index * 4 < m_ImgList[i].end) { m_ImgList[i].cacheCnt += 1; } } for (int m = 0; m < m_HrefList.Count; m++) { RichTextMgr.HrefInfo hrefInfo = m_HrefList[m]; if (index * 4 < hrefInfo.end) { hrefInfo.end_cache_cnt += 1; } if (index * 4 < hrefInfo.start) { hrefInfo.start_cache_cnt += 1; } } } private void CacluHrefAndImgIndex() { for (int i = 0; i < m_ImgList.Count; i++) { m_ImgList[i].end += m_ImgList[i].cacheCnt * 4; } for (int m = 0; m < m_HrefList.Count; m++) { RichTextMgr.HrefInfo hrefInfo = m_HrefList[m]; hrefInfo.start += hrefInfo.start_cache_cnt * 4; hrefInfo.end += hrefInfo.end_cache_cnt * 4; } } private float GetResolutionRatio() { return 1 / pixelsPerUnit; } public override string text { get { return m_Text; } set { if (string.IsNullOrEmpty(value)) { value = string.Empty; } m_RichText = value; SetRichTextDirty(); } } #if UNITY_EDITOR protected override void OnValidate() { text = m_Text; } #endif private void SetRichTextDirty() { m_RichText = UIHelper.ReplaceNewLine(m_RichText); m_OutputText = GetOutputText(m_RichText); if (AutoNewLine) { SetFitterSize(); } m_Text = m_OutputText; SetVerticesDirty(); SetLayoutDirty(); } #endregion }