一、UI与3D物体层叠时只响应UI
当我们的UI与场景中的3D物体发生层叠时,可能会出现点击UI时,3D物体与UI同时响应的问题。
例如给一个Cube和一个Image都挂载上“鼠标点击变色”的脚本
Cube上的脚本如下:
public class GameObjectEvent : MonoBehaviour
{
private void OnMouseDown()
{
ChangeColor();
}
private bool _isClicked;
private void ChangeColor()
{
if (_isClicked)
{
GetComponent<MeshRenderer>().material.color = Color.white;
}
else
{
GetComponent<MeshRenderer>().material.color = Color.gray;
}
_isClicked = !_isClicked;
}
}
Image上的脚本如下:
public class UIClickEvent : MonoBehaviour,IPointerClickHandler
{
private bool _isClicked;
public void OnPointerClick(PointerEventData eventData)
{
if (_isClicked)
{
GetComponent<Image>().color = Color.red;
}
else
{
GetComponent<Image>().color = Color.blue;
}
_isClicked = !_isClicked;
}
}
此时运行游戏,点击Image。我们会发现Cube和Image的事件被同时触发了
那么在点击UI时如何避免触发3D物体的事件呢?一种解决方案是让3D物体也通过实现事件系统接口来响应事件。
首先需要给Camera挂载「Physics Raycaster」组件(否则3D物体无法接收到射线),然后修改Cube身上的脚本
public class GameObjectEvent : MonoBehaviour,IPointerClickHandler
{
// private void OnMouseDown()
// {
// ChangeColor();
// }
private bool _isClicked;
private void ChangeColor()
{
if (_isClicked)
{
GetComponent<MeshRenderer>().material.color = Color.white;
}
else
{
GetComponent<MeshRenderer>().material.color = Color.gray;
}
_isClicked = !_isClicked;
}
public void OnPointerClick(PointerEventData eventData)
{
ChangeColor();
}
}
问题解决:
二、UI与3D物体层叠时同时响应
现在假设我们游戏中的物体全都使用了上面提到的实现接口响应事件的方案,但又希望在点击UI时,3D物体也同时响应,这又该如何实现呢?
EventSystem
中提供的RaycastAll
方法可以很好地解决这个问题。它的作用是让所有配置的「BaseRaycasters」都向场景中发射射线。也就是在点击时让UI中的「Graphic Raycaster」与相机中的「Physics Raycaster」同时发射射线。
具体的代码如下:
public class UIClickEvent : MonoBehaviour,IPointerClickHandler
{
private bool _isClicked;
public void OnPointerClick(PointerEventData eventData)
{
ChangeColor();
ExecuteAll(eventData);
}
private void ChangeColor()
{
if (_isClicked)
{
GetComponent<Image>().color = Color.red;
}
else
{
GetComponent<Image>().color = Color.blue;
}
_isClicked = !_isClicked;
}
public void ExecuteAll(PointerEventData eventData)
{
List<RaycastResult> results = new();
EventSystem.current.RaycastAll(eventData,results);
foreach (var raycastResult in results)
{
// 排除当前物体
if (raycastResult.gameObject != gameObject)
{
ExecuteEvents.Execute(raycastResult.gameObject, eventData, ExecuteEvents.pointerClickHandler);
}
}
}
}
将其挂载到UI元素上即可
三、点击UI时不触发输入事件
现在我们的Cube改用输入事件触发,即不管在任何地方按下鼠标左键,都会触发颜色改变事件
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
ChangeColor();
}
}
此时,再点击UI时也会触发Cube的颜色改变事件
那么,如何在点击UI时避免触发Cube的事件呢?我们很容易想到,UI的射线发射器是「Graphic Raycaster」,它是无法检测到3D物体的。因此可以在鼠标按下时,判断「Graphic Raycaster」是否检测到了UI,如果检测到了UI,就不再触发Cube的事件。
// 示例代码,不要在意性能
private void Update()
{
if (Input.GetMouseButtonDown(0) && !IsUI())
{
ChangeColor();
}
}
private bool IsUI()
{
GraphicRaycaster raycaster = FindObjectOfType<GraphicRaycaster>();
if (raycaster == null)
return false;
PointerEventData data = new PointerEventData(EventSystem.current);
data.pressPosition = Input.mousePosition;
data.position = Input.mousePosition;
List<RaycastResult> results = new();
raycaster.Raycast(data,results);
return results.Count > 0;
}
大功告成