一、简介
马三从上一家公司离职了,最近一直在出去面试,忙得很,所以这一篇博客拖到现在才写出来。马三在上家公司工作的时候,曾处理了一个UGUI不规则区域点击的问题,制作过程中也有一些收获和需要注意坑,因此记录成博客与大家分享。众所周知在UGUI中,响应点击通常是依附在一张图片上的,而图片不管美术怎么给你切,导进Unity之后都是一个矩形,如果要做其他形状,最多只能旋转一下,或者自己做一些处理。而为了美术效果,很多时候我们不得不需要特定形状的UI,并且让它们实现精准的响应点击。例如下图就是一个不规则的点击区域。
图1:UGUI不规则点击区域示意图
下面是处理了不规则区域点击后的演示效果,当点击按钮的时候,会对点击次数进行累加并且打印到控制台。可以看到进行了不规则区域点击处理以后,对我们原来的普通矩形Sprite的点击不会产生到影响,而不规则区域的表现效果也符合我们的预期。
图2:规则区域与不规则区域点击效果对比
二、针对UGUI不规则区域点击的两种处理方法
针对UGUI的不规则区域响应点击,一般来说有两种处理办法:
1.精灵像素检测:该方法是指通过读取精灵(Sprite)在某一点的像素值(RGBA),如果该点的像素值中的Alpha小于一定的阈值(比如0.5)则表示该点处是透明的,即用户点击的位置在精灵边界以外,否则用户点击的位置在精灵边界内部。
2.通过算法计算碰撞区域:通过一定的算法,手动计算出碰撞区域,然后在判断用户是点击在了精灵上面,还是点击在精灵外部。
1.精灵像素检测法
首先来说下精灵像素检测法,因为它实现起来比较简单也好理解。uGUI在处理控件是否被点击的时候,主要是根据IsRaycastLocationValid这个方法的返回值来进行判断的,而这个方法用到的基本原理则是判断指定点对应像素的RGBA数值中的Alpha是否大于某个指定临界值。例如,我们知道半透明通常是指Alpha=0.5,而对一个后缀名为png格式的图片来说半透明或者完全透明的区域理论上不应该被响应的,所以根据这个原理,我们只需要设定一个透明度的临界值,然后对当前鼠标位置对应的像素进行判断就可以了,因此这种方法叫做精灵像素检测。对于上面的这个IsRaycastLocationValid接口,我们可以通过下载UGUI源码或者反编译的方式看到它的实现:
1 public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) 2 { 3 //当透明度>=1.0时,表示点击在可响应区域返回true 4 if(this.m_EventAlphaThreshold >= 1f){ 5 return true; 6 } 7 8 //当没有指定精灵时返回true,因为不指定Spirte的时候,Unity将其区域填充为默认的白色,全部区域都是可以响应点击的 9 Sprite overrideSprite = this.overrideSprite;10 if(overrideSprite == null){11 return true;12 }13 14 //坐标系转换 15 Vector2 local;16 RectTransformUtility.ScreenPointToLocalPointInRectangle(base.rectTransform, screenPoint, eventCamera, ref local);17 Rect pixelAdjustedRect = base.GetPixelAdjustedRect ();18 local.x += base.rectTransform.get_pivot ().x * pixelAdjustedRect.get_width ();19 local.y += base.rectTransform.get_pivot ().y * pixelAdjustedRect.get_height ();20 local = this.MapCoordinate(local, pixelAdjustedRect);21 Rect textureRect = overrideSprite.get_textureRect ();22 Vector2 vector = new Vector2(local.x / textureRect.get_width (), local.y / textureRect.get_height ());23 24 //计算屏幕坐标对应的UV坐标25 float num = Mathf.Lerp(textureRect.get_x (), textureRect.get_xMax (), vector.x) / (float)overrideSprite.get_texture().get_width();26 float num2 = Mathf.Lerp(textureRect.get_y (), textureRect.get_yMax (), vector.y) / (float)overrideSprite.get_texture().get_height();27 bool result;28 29 //核心方法:像素检测30 try{31 result = (overrideSprite.get_texture().GetPixelBilinear(num, num