文章目录
- 前言
- 为什么不用Input类
- EventTrigger的坑
- 组件描述
- 它的坑
- 描述
- 具体表现
- 解决方案
- 具体代码
- 原理
- 结语
前言
我本来是使用Input类输入来直接写UI逻辑的
但是在我添加弹窗并用弹窗做一些操作的时候,发生了“穿透”现象
也就是在对弹窗做操作的时候,下层UI也对操作做出了反应
这个的罪魁祸首就是Input类,它在任何时候都会做出反应,并不能结合到Event的事件拦截
因此感悟:果然UI还是得用EventSystem+EventTrigger啊
为什么不用Input类
因为Input一定会被用于游戏内UI外的输入处理,它与EventSystem(UI的事件接收系统)是隔离的。
如果使用UI的时候使用Input来自行写方法的调用,那么就会出现在不需要调用某UI的方法的时候调用了的现象。
所以UI层还是少用Input(的鼠标)比较好
EventTrigger的坑
仅说我碰到的。
类似以下描述的坑应该均可以用本文的方法解决。
组件描述
- EventTrigger这个组件,熟悉Unity的人应该都知道,是为UI(UGUI)提供的事件触发组件。
- UI的操作基本都是和鼠标(或者触摸)相关的。
- 它已经封装好了许多的事件,我们使用的时候只需要添加相应的方法到对应的事件回调列表中即可,包括鼠标的按下/抬起/点击、鼠标拖拽的开始/进行/结束等。
它的坑
描述
鼠标的按下与抬起、拖拽的开始与结束是捆绑的!(我只用到了这两组,其他的同类关联性事件应该也类似)
具体表现
当为一个物体设置了鼠标的按下和抬起两个事件时(假设这个物体叫item),鼠标对其按下,然后移动到其他同样挂载有此事件的UI上(比如叫item001),然后再松开,它会调用的不是item001的抬起事件,而是item的抬起事件!
所以当你需要触发item001的抬起事件时会变得没法触发。
解决方案
本来试图直接从EventSystem接管UI事件的,但是好像还是得实现对应的handler(事件接口)才行,因此把目光放在了如何让trigger触发真实的射线检测到的UI的事件
我想到的方案是事件转发,也就是在触发事件后,调用真正需要调用的事件。
具体代码
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
namespace Assets.Scripts.Utils
{
/// <summary>
/// 重写的事件触发器
/// </summary>
public class UIEventListener : EventTrigger
{
public override void OnPointerDown(PointerEventData eventData)
{
// 检测并转发事件到正确的物体上
var triggerObject = eventData.pointerCurrentRaycast.gameObject;
Debug.Log($"OnPointerDown: {triggerObject} && {gameObject} && {triggerObject == gameObject}");
if (triggerObject != gameObject)
{
triggerObject?.GetComponent<EventTrigger>()?.OnPointerDown(eventData);
}
else
{
base.OnPointerDown(eventData);
}
}
public override void OnPointerUp(PointerEventData eventData)
{
// 检测并转发事件到正确的物体上
var triggerObject = eventData.pointerCurrentRaycast.gameObject;
Debug.Log($"OnPointerUp: {triggerObject} && {gameObject} && {triggerObject == gameObject}");
if (triggerObject != gameObject)
{
triggerObject?.GetComponent<EventTrigger>()?.OnPointerUp(eventData);
}
else
{
base.OnPointerUp(eventData);
}
}
public override void OnBeginDrag(PointerEventData eventData)
{
// 检测并转发事件到正确的物体上
var triggerObject = eventData.pointerCurrentRaycast.gameObject;
Debug.Log($"OnPointerDown: {triggerObject} && {gameObject} && {triggerObject == gameObject}");
if (triggerObject != gameObject)
{
triggerObject?.GetComponent<EventTrigger>()?.OnBeginDrag(eventData);
}
else
{
base.OnBeginDrag(eventData);
}
}
public override void OnEndDrag(PointerEventData eventData)
{
// 检测并转发事件到正确的物体上
var triggerObject = eventData.pointerCurrentRaycast.gameObject;
Debug.Log($"OnPointerDown: {triggerObject} && {gameObject} && {triggerObject == gameObject}");
if (triggerObject != gameObject)
{
triggerObject?.GetComponent<EventTrigger>()?.OnEndDrag(eventData);
}
else
{
base.OnEndDrag(eventData);
}
}
}
}
原理
主要就是利用了事件会传递的参数信息以及C#的特点。
- 首先要继承自EventTrigger,重写里边事件触发的方法,可以对其做预处理等
- 传递的PointerEventData类型参数eventData包含有当前实际的射线检测到的目标UI信息,从中获得对应的UI物体(GameObject),然后和挂载着这个脚本的物体(Component都可以直接获取挂载着它的GameObject)作比较。由于GameObject是类,也就是引用类型,因此直接用==比较即可知道他们是否是同一个。
- 如果是同一个物体,就直接调用自身的事件(base.OnXXX());如果不是同一个物体,就调用eventData中的物体身上的EventTrigger组件(子类当然可行)的对应事件(XXX.GetComponent().OnXXX(eventData))即可
至于上面写的
triggerObject?.GetComponent<EventTrigger>()?.OnEndDrag(eventData);
中的问号的部分,相当于以下代码:
if (triggerObject != null){
if (triggerObject.GetComponent<EventTrigger>() != null){
triggerObject.GetComponent<EventTrigger>().OnEndDrag(eventData);
}
}
也就是C#提供的对于空值检测的简写,方便编程(调用方法的时候才能用)
结语
今天踩到的坑就这么解决啦!
如果有什么疑问,欢迎评论。觉得我写的有问题也可以说。
那么,祝大家开发能顺顺利利!