0.前言
游戏开发中,我们可能经常需要处理UI或者模型的触碰事件响应。
有时两者会处于叠加关系,但是我们只想让其中一方响应,比如如下图情况
有时我们也想让点击事件有穿透效果
有时候我们也想让鼠标点击屏幕任意位置时模型都有响应,但是会被UI拦截。这种情况常用于处理系统UI或者技能UI不响应鼠标点击事件
1.实现思路
实现方法有很多,比如使用OnMouseDown方法,但是这里会有一些弊端,比如实现了这个方法后,UI不会对事件进行拦截。
这里我使用了一套统一的解决方案,可以同时解决以上所有情况,并且思路还比较清晰。
核心思路就是使用PhysicRaycaster组件和EventTrigger
首先GraphicRaycaster只能识别UGUI那一套继承自Graphic基类的组件,3D物体并不能使用。而我们需要一套通用的解决方案,使用PhysicRaycaster的话,所以两者都能响应。
2.单独响应的实现细节
注意,在此我只是提供一种思路,代码并不严谨,都是方便理解随便写的
首先需要把PhysicRaycaster组件挂在到摄像机上
然后在给UI或者3D物体添加一个脚本,并在脚本中实现IPointHandler接口
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Interaction : MonoBehaviour,IPointerClickHandler
{
public InteracitonType interacitonType;
private Image m_Image;
private int m_Index = 0;
void Start()
{
m_Image = GetComponent<Image>();
}
public void OnPointerClick(PointerEventData eventData)
{
ChangeUIColorByPointClick();
}
// 当点击UI的时候改变UI颜色
private void ChangeUIColorByPointClick()
{
if (m_Index == 0)
{
m_Image.color = Color.red;
}
else
{
m_Image.color = Color.blue;
}
m_Index = m_Index == 0 ? 1 : 0;
}
}
3D物体的脚本写法同理,改变材质球的颜色,然后把对应的脚本添加到对应的对象上就可以实现图一所示效果,自己只负责自己的响应事件,互不影响。
3.事件穿透的实现细节
使用EventSystem.current.RaycastAll()方法可以检测到当前点击位置的所有对象
接下来去遍历这些对象,只要他们实现了IPointerHandler接口,就是使用ExecuteEvents.Execute方法去执行对应的点击事件
假如UI在最上面,那么就把这段代码加在UI脚本中,代码细节如下
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Interaction : MonoBehaviour,IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
ChangeModelColorByClickOnUI(eventData);
}
// 当点击模型上方的UI时,模型可以相应到点击事件
private void ChangeModelColorByClickOnUI(PointerEventData eventData)
{
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, results);
foreach (RaycastResult raycastResult in results)
{
if (raycastResult.gameObject != gameObject)
{
ExecuteEvents.Execute(raycastResult.gameObject, eventData, ExecuteEvents.pointerClickHandler);
}
}
}
}
4.UI拦截屏幕滑动事件的实现细节
具体情况对应图3,当使用手或者鼠标滑动屏幕的时候,3D响应,当滑动过程中,触碰到UI或者系统UI的时候,停止对3d的响应。
这里使用GraphicRaycaster判断鼠标是否点击到了UI,当射线检测数量大于0时,说明检测到了UI。这样做的好处是如果项目中以上所有情况都会出现,也不会出现逻辑上的冲突,因为前面提到了GraphicRaycaster只能识别UGUI那一套继承自Graphic基类的组件,3D物体不会出现响应。
代码细节如下
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class Interaction : MonoBehaviour
{
private GraphicRaycaster m_Raycaster;
private MeshRenderer m_MeshRender;
private int m_Index = 0;
// Start is called before the first frame update
void Start()
{
// 因为是做示例效果,此处并不严谨
m_Raycaster = FindObjectOfType<GraphicRaycaster>();
m_MeshRender = GetComponent<MeshRenderer>();
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButton(0))
{
ChangeModelColorByMouseDown();
}
}
// 当点击屏幕时,模型响应事件,但是点击到UI上时,ui拦截事件,模型不再响应
private void ChangeModelColorByMouseDown()
{ PointerEventData data = new PointerEventData(EventSystem.current);
data.pressPosition = Input.mousePosition;
data.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
m_Raycaster.Raycast(data, results);
if (results.Count == 0)
{
ChangeModelColorByPointClick();
}
}
// 当点击模型时,模型可以响应点击事件
private void ChangeModelColorByPointClick()
{
if (m_Index == 0)
{
m_MeshRender.material.SetColor("_Color", Color.blue);
}
else
{
m_MeshRender.material.SetColor("_Color", Color.red);
}
m_Index = m_Index == 0 ? 1 : 0;
}
}
5.写在最后
这些思路都是笔者为了方便解释思路,临时编写的代码,可能有很多不严谨的地方,但是自觉足以给大家提供一个这类问题的统一解决方案,并且实践起来也并不复杂。
大家也可以尝试把这类问题统一封装成一个脚本,提供一个枚举类型,根据实际情况选择对应的方案,达到一劳永逸的效果。