纵观网络上绝大部分讲到UGUI原理的文章,在谈及点击判断时,都认为是通过射线检测来进行判断的,笔者曾经也对此深信不疑,直到前段时间亲自阅读了UGUI的C#源码,方才发现并非如此,那么UGUI究竟是如何进行点击判断的呢?接下来便由笔者通过对源码的逐步分析来探究这个问题。
![f1c382cae941cd6bfa8d060a8c1ce821.png](https://img-blog.csdnimg.cn/img_convert/f1c382cae941cd6bfa8d060a8c1ce821.png)
打开EventSystem类,会看到里面使用一个列表来保存所有BaseInputModule。
![0c0acea194d7c0c1d422cf73e2a09e71.png](https://img-blog.csdnimg.cn/img_convert/0c0acea194d7c0c1d422cf73e2a09e71.png)
![9e618cf5e028930c50e628b8164820f7.png](https://img-blog.csdnimg.cn/img_convert/9e618cf5e028930c50e628b8164820f7.png)
BaseInputModule是负责检测并处理玩家输入的组件,会在自身OnEnable和OnDisable时调用EventSystem.UpdateModels更新列表。
![a58c863b7568cd3cbec54fd82c1d3f59.png](https://img-blog.csdnimg.cn/img_convert/a58c863b7568cd3cbec54fd82c1d3f59.png)
EventSystem会在每次Update时,去调用CurrentInputModule的Process方法进行输入处理。
BaseInputModule有一个子类:StandaloneInputModule,即标准输入模块,是在UGUI事件系统中所默认使用,所以此处可以右键转到StandaloneInputModule类中对Process的实现进行查看。
![74579082d801e07e8ae6289d6e6fdd96.png](https://img-blog.csdnimg.cn/img_convert/74579082d801e07e8ae6289d6e6fdd96.png)
可以看到这里进行了很多处理,不过在暂时只需要关注ProcessTouchEvents即可。
![b85b0c17384e223a9ca0f2d2049cdfdd.png](https://img-blog.csdnimg.cn/img_convert/b85b0c17384e223a9ca0f2d2049cdfdd.png)
在ProcessTouchEvents中,通过调用input.GetTouch获取到Touch对象(input属性是BaseInput类型的,而BaseInput类又是对Input类的封装)以得到被点击位置的信息。然后通过调用基类PointerInputModule的GetTouchPointerEventData方法获取到PinterEventData对象,最后借助PointerEventData进行各种事件处理。
那么接下来的目标就很明确了,转至GetTouchPointerEventData方法中。
![6aae50b2867109b85998f5b97aa1ec60.png](https://img-blog.csdnimg.cn/img_convert/6aae50b2867109b85998f5b97aa1ec60.png)
其中又通过调用EventSystem.RaycastAll获取到所有被点击物体,然后找到排第一的来作为此次点击到的物体,让我们转至EventSystem.RaycastAll方法中。
![cda8d2a54af99b6d47b180bc7b1b1b74.png](https://img-blog.csdnimg.cn/img_convert/cda8d2a54af99b6d47b180bc7b1b1b74.png)
在RaycastAll方法中,首先通过RaycasterManager获取到所有BaseRaycaster对象,然后依次调用这些Raycaster的Raycast方法获取到被点击的物体。
BaseRaycaster.Raycast这个方法,从命名上看,和Physics上的Raycast一样,那么是否真的是在其中通过射线来检测UI点击的呢?让我们右键转到UI专用的GraphicRaycaster的Raycast实现中。
![e7d1247faf7aaed94084300fbaf2e317.png](https://img-blog.csdnimg.cn/img_convert/e7d1247faf7aaed94084300fbaf2e317.png)
可以看到里面有一长串的代码。
![a989c6f064c00e2b45e0e0e67980fe88.png](https://img-blog.csdnimg.cn/img_convert/a989c6f064c00e2b45e0e0e67980fe88.png)
往下翻,可以看到这里创建了Ray对象,看到这里,或许会有人认为点击判断看来的确是由射线完成的,别急,让我们继续往下翻,看看这个射线被用到了哪里。
![eb8069bad2a5e4332659645a5fd64e53.png](https://img-blog.csdnimg.cn/img_convert/eb8069bad2a5e4332659645a5fd64e53.png)
可以看出,这个射线对象,是只有要进行遮挡对象检测时,才会被用到的,也就是说这个射线对象是用来检测2D/3D物体对UI的遮挡的,而不是用来检测UI点击的,那么UI点击究竟是如何进行检测的呢?让我们继续往下翻。
![80a0ae37365d1106de15afdba3542518.png](https://img-blog.csdnimg.cn/img_convert/80a0ae37365d1106de15afdba3542518.png)
此处,调用了另一个Raycast重载,来获取到被点击的Graphic列表,转至这个Raycast中。
![30eaf5ee2a62a79ded5c579efb32ea92.png](https://img-blog.csdnimg.cn/img_convert/30eaf5ee2a62a79ded5c579efb32ea92.png)
在这个Raycast中,调用了该Raycaster所属Canvas中的所有符合条件的Graphic的Raycast方法,根据返回值决定是否将其最终放入results列表中。
接下来转至Graphic的Raycast方法中。
![ce929f78f08562a190dfcd8736048ac3.png](https://img-blog.csdnimg.cn/img_convert/ce929f78f08562a190dfcd8736048ac3.png)
![724f6d2a8ef396369a7a8b0e7b47734d.png](https://img-blog.csdnimg.cn/img_convert/724f6d2a8ef396369a7a8b0e7b47734d.png)
首先会获取该Graphic所在物体的所有组件,然后遍历这些组件,根据组件类型进行处理。其中比较重要的是对ICanvasRaycastFilter的IsRaycastLocationValid的调用,会根据这个调用的返回值来决定该Graphic是否被点击了。ICanvasRaycastFilter接口有多个实现类,笔者此处以最常见的Image为例,转至Image所实现的IsRaycastLocationValid中。
![08e5784e6a659ff252430b665b00913d.png](https://img-blog.csdnimg.cn/img_convert/08e5784e6a659ff252430b665b00913d.png)
其中进行了大量的坐标计算,并以最终计算结果来判断是否被点击到,其中并无任何涉及物理射线的地方。
到这里,便可以回答本文开头所提出的问题了:UGUI中的点击判断,是通过坐标计算得出的,而非是之前广泛流传的射线检测。
至于为何之前存在如此广泛的误解,笔者猜想大概是被Raycast这个极具迷惑性的API命名所误导,而又未深入阅读源码所导致的。
另外,本文之前所探究的都是GraphicRaycaster下的点击判断原理,那么PhysicsRaycaster与Physics2DRaycaster下的点击判断又是如何检测的呢?与GraphicRaycaster的复杂调用不同,只需看下此二者者的Raycast实现便可知晓。
PhysicsRaycaster:
![9e7e9513ad5a0a4ca14adf29ded5add1.png](https://img-blog.csdnimg.cn/img_convert/9e7e9513ad5a0a4ca14adf29ded5add1.png)
Physics2DRaycaster:
![19702a4c7e4e7b04358b4bb09448cb3e.png](https://img-blog.csdnimg.cn/img_convert/19702a4c7e4e7b04358b4bb09448cb3e.png)
可以看出,二者在实现上是极为相似的,与GrahicRaycaster通过计算坐标判断UI点击不同,PhysicsRaycaster与Physics2DRaycaster都是通过物理射线来进行2D/3D物体的点击判断。