点击区域可以分成两部分来分析:
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, "\r\n"); 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 }
效果如下图。其中蓝色区域即为点击区域。绑定监听后,点击图片或下划线上的文字,即可看到事件被响应了。