[UGUI]图文混排(六):点击区域

点击区域可以分成两部分来分析:

0.Rect

搜索api:Rect和Rect.Rect,可以知道:

在GUI和GUILayout中,Rect的原点在左上角,向右为x轴正方向,向下为y轴正方向;

除此之外,其他情况下Rect的原点在左下角,向右为x轴正方向,向上为y轴正方向。

1.区域的判定

a.图片的可点击区域:整张图片

b.文字的可点击区域:下划线上的文字

2.点击响应

计算出区域后,因为这个区域是局部坐标系的,再将点击坐标转换为text中的局部坐标,判定该坐标是否在区域内,即可完成点击响应。

综上,可以得出如下的代码:

RichTextEvent.cs

1 using UnityEngine;
2 
3 public class RichTextEvent {
4 
5     public Rect rect;//触发事件的判定区域
6     public string name;//事件名
7     public string parameter;//事件参数
8 }

RichText.cs

  1 using System.Collections.Generic;
  2 using System.Text.RegularExpressions;
  3 using System.Text;
  4 using UnityEngine.EventSystems;
  5 using System;
  6 using UnityEngine;
  7 using UnityEngine.UI;
  8 
  9 //图片<icon name=*** w=1 h=1 n=*** p=***/>
 10 //下划线<material=underline c=#ffffff h=1 n=*** p=***>blablabla...</material>
 11 public class RichText : Text, IPointerClickHandler {
 12 
 13     private FontData fontData = FontData.defaultFontData;
 14 
 15     //--------------------------------------------------------图片 start
 16     private static readonly string replaceStr = "u00A0";
 17     private static readonly Regex imageTagRegex = new Regex(@"<icon name=([^>s]+)([^>]*)/>");//(名字)(属性)
 18     private static readonly Regex imageParaRegex = new Regex(@"(w+)=([^s]+)");//(key)=(value)
 19     private List<RichTextImageInfo> imageInfoList = new List<RichTextImageInfo>();
 20     private bool isImageDirty = false;
 21     //--------------------------------------------------------图片 end
 22 
 23     //--------------------------------------------------------文字 start
 24     private RichTextParser richTextParser = new RichTextParser();
 25     //--------------------------------------------------------文字 end
 26 
 27     //--------------------------------------------------------事件 start
 28     private Action<string, string> clickAction;
 29     private List<RichTextEvent> eventList = new List<RichTextEvent>();
 30     //--------------------------------------------------------事件 end
 31 
 32     //--------------------------------------------------------调试 start
 33     private bool isShowClickArea = true;//是否显示可点击区域
 34     //--------------------------------------------------------调试 end
 35 
 36     protected RichText()
 37     {
 38         fontData = typeof(Text).GetField("m_FontData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(this) as FontData;
 39     }
 40 
 41     readonly UIVertex[] m_TempVerts = new UIVertex[4];
 42     protected override void OnPopulateMesh(VertexHelper toFill)
 43     {
 44         if (font == null)
 45             return;
 46 
 47         // We don't care if we the font Texture changes while we are doing our Update.
 48         // The end result of cachedTextGenerator will be valid for this instance.
 49         // Otherwise we can get issues like Case 619238.
 50         m_DisableFontTextureRebuiltCallback = true;
 51 
 52         //处理事件
 53         eventList.Clear();
 54 
 55         //处理图片标签
 56         string richText = text;
 57         IList<UIVertex> verts = null;
 58         richText = CalculateLayoutWithImage(richText, out verts);
 59 
 60         //处理文字标签
 61         List<RichTextTag> tagList = null;
 62         richTextParser.Parse(richText, out tagList);
 63         for (int i = 0; i < tagList.Count; i++)
 64         {
 65             RichTextTag tag = tagList[i];
 66             switch (tag.tagType)
 67             {
 68                 case RichTextTagType.None:
 69                     break;
 70                 case RichTextTagType.Underline:
 71                     ApplyUnderlineEffect(tag as RichTextUnderlineTag, verts);
 72                     break;
 73                 default:
 74                     break;
 75             }
 76         }
 77 
 78         //Vector2 extents = rectTransform.rect.size;
 79 
 80         //var settings = GetGenerationSettings(extents);
 81         //cachedTextGenerator.Populate(text, settings);
 82 
 83         Rect inputRect = rectTransform.rect;
 84 
 85         // get the text alignment anchor point for the text in local space
 86         Vector2 textAnchorPivot = GetTextAnchorPivot(fontData.alignment);
 87         Vector2 refPoint = Vector2.zero;
 88         refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x);
 89         refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y);
 90 
 91         // Determine fraction of pixel to offset text mesh.
 92         Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint;
 93 
 94         // Apply the offset to the vertices
 95         //IList<UIVertex> verts = cachedTextGenerator.verts;
 96         float unitsPerPixel = 1 / pixelsPerUnit;
 97         //Last 4 verts are always a new line...
 98         int vertCount = verts.Count - 4;
 99 
100         toFill.Clear();
101         if (roundingOffset != Vector2.zero)
102         {
103             for (int i = 0; i < vertCount; ++i)
104             {
105                 int tempVertsIndex = i & 3;
106                 m_TempVerts[tempVertsIndex] = verts[i];
107                 m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
108                 m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
109                 m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
110                 if (tempVertsIndex == 3)
111                     toFill.AddUIVertexQuad(m_TempVerts);
112             }
113         }
114         else
115         {
116             //Debug.Log(unitsPerPixel);
117             for (int i = 0; i < vertCount; ++i)
118             {
119                 int tempVertsIndex = i & 3;
120                 m_TempVerts[tempVertsIndex] = verts[i];
121                 m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
122                 if (tempVertsIndex == 3)
123                     toFill.AddUIVertexQuad(m_TempVerts);
124                 //Debug.LogWarning(i + "_" + tempVertsIndex + "_" + m_TempVerts[tempVertsIndex].position);
125             }
126         }
127         m_DisableFontTextureRebuiltCallback = false;
128     }
129 
130     protected string CalculateLayoutWithImage(string richText, out IList<UIVertex> verts)
131     {
132         Vector2 extents = rectTransform.rect.size;
133         var settings = GetGenerationSettings(extents);
134 
135         float unitsPerPixel = 1 / pixelsPerUnit;
136 
137         float spaceWidth = cachedTextGenerator.GetPreferredWidth(replaceStr, settings) * unitsPerPixel;
138 
139         float fontSize2 = fontSize * 0.5f;
140 
141         //解析图片标签,并将标签替换为空格
142         imageInfoList.Clear();
143         Match match = null;
144         StringBuilder builder = new StringBuilder();
145         while ((match = imageTagRegex.Match(richText)).Success)
146         {
147             RichTextImageInfo imageInfo = new RichTextImageInfo();
148             imageInfo.name = match.Groups[1].Value;
149             string paras = match.Groups[2].Value;
150             if (!string.IsNullOrEmpty(paras))
151             {
152                 var keyValueCollection = imageParaRegex.Matches(paras);
153                 for (int i = 0; i < keyValueCollection.Count; i++)
154                 {
155                     string key = keyValueCollection[i].Groups[1].Value;
156                     string value = keyValueCollection[i].Groups[2].Value;
157                     imageInfo.SetValue(key, value);
158                 }
159             }
160             imageInfo.size = new Vector2(fontSize2 * imageInfo.widthScale, fontSize2 * imageInfo.heightScale);
161             imageInfo.startVertex = match.Index * 4;
162             int num = Mathf.CeilToInt(imageInfo.size.x / spaceWidth);//占据几个空格
163             imageInfo.vertexLength = num * 4;
164             imageInfoList.Add(imageInfo);
165 
166             builder.Length = 0;
167             builder.Append(richText, 0, match.Index);
168             for (int i = 0; i < num; i++)
169             {
170                 builder.Append(replaceStr);
171             }
172             builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
173             richText = builder.ToString();
174         }
175 
176         // Populate charaters
177         cachedTextGenerator.Populate(richText, settings);
178         verts = cachedTextGenerator.verts;
179         // Last 4 verts are always a new line...
180         int vertCount = verts.Count - 4;
181 
182         //换行处理
183         //0 1|4 5|8  9
184         //3 2|7 6|11 10
185         //例如前两个字为图片标签,第三字为普通文字;那么startVertex为0,vertexLength为8
186         for (int i = 0; i < imageInfoList.Count; i++)
187         {
188             RichTextImageInfo imageInfo = imageInfoList[i];
189             int startVertex = imageInfo.startVertex;
190             int vertexLength = imageInfo.vertexLength;
191             int maxVertex = Mathf.Min(startVertex + vertexLength, vertCount);
192             //如果最边缘顶点超过了显示范围,则将图片移到下一行
193             //之后的图片信息中的起始顶点都往后移
194             if (verts[maxVertex - 2].position.x * unitsPerPixel > rectTransform.rect.xMax)
195             {
196                 richText = richText.Insert(startVertex / 4, "
");
197                 for (int j = i; j < imageInfoList.Count; j++)
198                 {
199                     imageInfoList[j].startVertex += 8;
200                 }
201                 cachedTextGenerator.Populate(richText, settings);
202                 verts = cachedTextGenerator.verts;
203                 vertCount = verts.Count - 4;
204             }
205         }
206 
207         //计算位置
208         for (int i = imageInfoList.Count - 1; i >= 0; i--)
209         {
210             RichTextImageInfo imageInfo = imageInfoList[i];
211             int startVertex = imageInfo.startVertex;
212             if (startVertex < vertCount)
213             {
214                 UIVertex uiVertex = verts[startVertex];
215                 Vector2 pos = uiVertex.position;
216                 pos *= unitsPerPixel;
217                 pos += new Vector2(imageInfo.size.x * 0.5f, fontSize2 * 0.5f);
218                 pos += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
219                 imageInfo.position = pos;
220                 imageInfo.color = Color.white;
221 
222                 if (!string.IsNullOrEmpty(imageInfo.eventName))
223                 {
224                     //图片pos:
225                     //x:起点x + 图片宽度的一半
226                     //y:起点y + fontSize2 * 0.5f
227                     RichTextEvent e = new RichTextEvent();
228                     e.name = imageInfo.eventName;
229                     e.parameter = imageInfo.eventParameter;
230                     e.rect = new Rect(
231                         verts[startVertex].position.x * unitsPerPixel,
232                         verts[startVertex].position.y * unitsPerPixel + fontSize2 * 0.5f - imageInfo.size.y * 0.5f,
233                         imageInfo.size.x,
234                         imageInfo.size.y
235                     );
236                     eventList.Add(e);
237                 }
238             }
239             else
240             {
241                 imageInfoList.RemoveAt(i);
242             }
243         }
244 
245         isImageDirty = true;
246 
247         return richText;
248     }
249 
250     private void ApplyUnderlineEffect(RichTextUnderlineTag tag, IList<UIVertex> verts)
251     {
252         float fontSize2 = fontSize * 0.5f;
253         float unitsPerPixel = 1 / pixelsPerUnit;
254 
255         //0 1|4 5|8  9 |12 13
256         //3 2|7 6|11 10|14 15
257         //<material=underline c=#ffffff h=1 n=1 p=2>下划线</material>
258         //以上面为例:
259         //tag.start为42,对应“>” | start对应“下”的左上角顶点
260         //tag.end为44,对应“划”  | end对应“线”下一个字符的左上角顶点
261         //Debug.Log(tag.start);
262         //Debug.Log(tag.end);
263         int start = tag.start * 4;
264         int end = Mathf.Min(tag.end * 4 + 4, verts.Count);
265         UIVertex vt1 = verts[start + 3];
266         UIVertex vt2;
267         float minY = vt1.position.y;
268         float maxY = verts[start].position.y;
269 
270         //换行处理,如需换行,则将一条下划线分割成几条
271         //顶点取样分布,如上图的2,6,10,其中end - 2表示最后一个取样点,即10
272         //对应例子中的下、划、线的右下角顶点
273         for (int i = start + 2; i <= end - 2; i += 4)
274         {
275             vt2 = verts[i];
276             bool newline = Mathf.Abs(vt2.position.y - vt1.position.y) > fontSize2;
277             if (newline || i == end - 2)
278             {
279                 RichTextImageInfo imageInfo = new RichTextImageInfo();
280 
281                 //计算宽高
282                 int tailIndex = !newline && i == end - 2 ? i : i - 4;
283                 vt2 = verts[tailIndex];
284                 minY = Mathf.Min(minY, vt2.position.y);
285                 maxY = Mathf.Max(maxY, verts[tailIndex - 1].position.y);
286                 imageInfo.size = new Vector2((vt2.position.x - vt1.position.x) * unitsPerPixel, tag.height);
287 
288                 //计算位置
289                 Vector2 vertex = new Vector2(vt1.position.x, minY);
290                 vertex *= unitsPerPixel;
291                 vertex += new Vector2(imageInfo.size.x * 0.5f, -tag.height * 0.5f);
292                 vertex += new Vector2(rectTransform.sizeDelta.x * (rectTransform.pivot.x - 0.5f), rectTransform.sizeDelta.y * (rectTransform.pivot.y - 0.5f));
293                 imageInfo.position = vertex;
294 
295                 imageInfo.color = tag.color;
296                 imageInfoList.Add(imageInfo);
297 
298                 if (!string.IsNullOrEmpty(tag.eventName))
299                 {
300                     //下划线pos:
301                     //x:vt1.x + 图片宽度的一半
302                     //y:minY - tag.height * 0.5f
303                     RichTextEvent e = new RichTextEvent();
304                     e.name = tag.eventName;
305                     e.parameter = tag.eventParameter;
306                     e.rect = new Rect(
307                         vt1.position.x * unitsPerPixel,
308                         minY * unitsPerPixel,
309                         imageInfo.size.x,
310                         (maxY - minY) * unitsPerPixel
311                     );
312                     eventList.Add(e);
313                 }
314 
315                 vt1 = verts[i + 1];
316                 minY = vt1.position.y;
317                 if (newline && i == end - 2) i -= 4;
318             }
319             else
320             {
321                 minY = Mathf.Min(minY, verts[i].position.y);
322                 maxY = Mathf.Max(maxY, verts[i - 1].position.y);
323             }
324         }
325     }
326 
327     public void SetListener(Action<string, string> action)
328     {
329         clickAction = action;
330     }
331 
332     public void OnPointerClick(PointerEventData eventData)
333     {
334         Vector2 localPos;
335         RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out localPos);
336         for (int i = eventList.Count - 1; i >= 0; i--)
337         {
338             RichTextEvent e = eventList[i];
339             if (e.rect.Contains(localPos))
340             {
341                 clickAction.Invoke(e.name, e.parameter);
342                 break;
343             }
344         }
345     }
346 
347     protected void Update()
348     {
349         if (isImageDirty)
350         {
351             isImageDirty = false;
352 
353             //回收当前的图片
354             Image[] images = GetComponentsInChildren<Image>(true);
355             for (int i = 0; i < images.Length; i++)
356             {
357                 RichTextResourceManager.Instance.SetPoolObject(RichTextResourceType.Image, images[i].gameObject);
358             }
359 
360             //生成图片
361             for (int i = 0; i < imageInfoList.Count; i++)
362             {
363                 RichTextImageInfo imageInfo = imageInfoList[i];
364                 var name = imageInfo.name;
365                 var position = imageInfo.position;
366                 var size = imageInfo.size;
367                 var color = imageInfo.color;
368 
369                 GameObject go = RichTextResourceManager.Instance.GetPoolObject(RichTextResourceType.Image);
370                 Image image = go.GetComponent<Image>();
371                 RichTextResourceManager.Instance.SetSprite(name, image);
372                 go.transform.SetParent(rectTransform);
373                 go.transform.localScale = Vector3.one;
374                 image.rectTransform.anchoredPosition = position;
375                 image.rectTransform.sizeDelta = size;
376                 image.color = color;
377             }
378 
379             //点击区域
380             if (isShowClickArea)
381             {
382                 for (int i = 0; i < eventList.Count; i++)
383                 {
384                     RichTextEvent e = eventList[i];
385 
386                     GameObject go = RichTextResourceManager.Instance.GetPoolObject(RichTextResourceType.Image);
387                     Image image = go.GetComponent<Image>();
388                     RichTextResourceManager.Instance.SetSprite("", image);
389                     go.transform.SetParent(rectTransform);
390                     go.transform.localScale = Vector3.one;
391                     image.rectTransform.anchoredPosition = e.rect.center;
392                     image.rectTransform.sizeDelta = new Vector2(e.rect.width, e.rect.height);
393                     image.color = Color.blue;
394                 }
395             }
396         }
397     }
398 
399     protected override void OnDestroy()
400     {
401         base.OnDestroy();
402         //Debug.LogWarning("OnDestroy");
403     }
404 }

效果如下图。其中蓝色区域即为点击区域。绑定监听后,点击图片或下划线上的文字,即可看到事件被响应了。

原文地址:https://www.cnblogs.com/lyh916/p/9343908.html