|
开文来记录一下自己摸索到的UI事件接口的一些运作机制(坑),就不赘述具体怎么使用这些事件接口了(不做教学...),本文主要是记自己发现的一些坑点
主要针对于在复杂的UI嵌套关系下,鼠标指针和拖拽操作的事件接口的调用关系,一些容易出错的地方进行记录
欢迎各路神仙留言指正,若能不吝赐教,鄙人先谢为敬
版本:Unity 2019.3.0
emmmmmm
怎么好像之前Unity2018的跨父对象层级的Drop方法还无法正常调用来着,做背包系统的时候真的坑死了,现在2019这个坑是被官方填了?
<hr/>
1.还是提一下什么是UI事件接口
在UGUI中通过 using UnityEngine.EventSystems;
可以让继承自Mono的脚本实现一些丰富的事件接口,这些被实现接口方法会在进行UI交互的一些特定的行为出现时被调用
主要包括Point鼠标指针操作,Drag/Drop拖拽操作,Select点选操作,Input鼠标键盘输入类
UI事件接口主要用于UI上的一些物体(Image,Button一类),也可以应用到场景中一些3D物体
UI事件接口能够在 PC鼠标 和 触屏 两种操作模式下被正确的使用
在使用中UI事件接口与EventTrigger实现的交互事件区别在于,当某个UI物体需要被设计为一个对象,在针对它的特定的交互行为出现时调用该对象自身的一些方法,实现一些交互功能时,应使用UI事件接口的实现方法,将脚本挂在该物体上。而当某个UI物体不需要被设计为一个对象,在针对它的特定的交互行为出现时,需要去调用别的物体上某个组件的方法来实现一些交互功能时,应使用EventTrigger的实现方法,添加EventTrigger,添加交互行为并绑定别的物体上特定组件的特定方法
UI事件接口和EventTrigger能被正确调用的预备条件一致,即:
针对UI物体时,场景中应创建EventSystem,UI物体的RectTransform需要有一定的宽高
针对3D物体时,场景中应创建EventSystem,摄像机应添加Physics Raycaster组件,3D物体需要有Collider组件
<hr/>
2.准备一下测试
场景物体摆放即父子关系
分别给两个Panel添加一个名为 Test_IEP的脚本
给所有 Button 和 Image 都添加一个名为 Test_IEC的脚本
<hr/>
3.IPointer 鼠标点击事件接口测试
---3.1 父子均实现 IPointerDownHandler, IPointerUpHandler (出现覆盖问题)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEP : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public void OnPointerUp(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnPointerUp&#34;);
}
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnPointerDown&#34;);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEC : MonoBehaviour,IPointerDownHandler,IPointerUpHandler
{
public void OnPointerUp(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnPointerUp&#34;);
}
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnPointerDown&#34;);
}
}
------3.1.1 分别点击Panel没有被子对象覆盖的区域,和子对象
我们点击Panel没有被覆盖子对象的部分和子对象
在父子均实现IPointerDownHandler, IPointerUpHandler时,点击子对象覆盖住父对象的范围内时,只触发子对象的接口方法
------3.1.2 同级别下有遮挡时点击重和部分
同级别均实现IPointerDownHandler, IPointerUpHandler,UI物体有重和时,点击重和部分,仅触发最上层被点击到的UI对象的接口方法
补充一下:Hierarchy菜单,UI物体中越靠下的物体,才是越接近最上层的
---3.2 仅父对象实现 IPointerDownHandler, IPointerUpHandler (按钮是个特例)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEC : MonoBehaviour
{
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEP : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public void OnPointerUp(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnPointerUp&#34;);
}
public void OnPointerDown(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnPointerDown&#34;);
}
}
点击按钮,父对象的接口方法没有被触发
但点击Image正常触发父对象的接口方法
仅父对象实现 IPointerDownHandler, IPointerUpHandler 时,点击子对象中的按钮仍无法触发父对象的接口方法(可以理解为按钮组件本身就有IPointer接口)依然覆盖掉了点击向父对象的传递。但点击到子对象中Image的一类本身不具有交互性的子对象,可以正常触发父对象的接口方法
我们取消按钮的可交互性,点击按钮依然无法触发父对象的接口方法
将按钮组件UnEnable时,点击按钮能够触发父对象的方法
---3.3 重合时仅下层实现 IPointerDownHandler, IPointerUpHandler (遮挡依然存在)
不论下层是否是按钮还是Image,点击重合部分,下层物体实现的接口方法都不会被触发
自然上下层都实现肯定只调用上层实现的接口方法
---3.4 只实现IPointerUpHandler时(无法正常被调用)
只实现IPointerUpHandler根本无法正常调用,必须实现IPointerDownHandler,才能正常传递,调用IPointerUpHandler的接口方法
---3.5 IPointerClickHandler(与3.1,3.2出现的情况类似)
参见3.1 和 3.2 测试结果类似,出现子对象覆盖,同级只触发最上层,以及按钮的特例覆盖效应
<hr/>
4.Drag/Drop 拖拽接口测试
先补充几点:
1.UI事件接口的onDrag方法在开始拖动后,如果中途停止拖动(不松开鼠标但不移动)不会被调用,而我们有测试过onMouseDrag方法,开始拖动后即使中途拖动停止,依然会被调用
2.即使在极端状况下,IPointerDownHandler 也一定会先于 IBeginDragHandler被调用
3.可以通过eventData.pointerDrag.name 获取拖动启发的对象,并经常结合onDrop使用
4.IBeginDragHandler,IDragHandler,IEndDragHandler调用时均调用启发拖拽的物体,而IDropHandler是拖拽到的物体(结束拖拽是鼠标停留的物体)
5.通过eventData.Dragging可以判断是否处于拖拽状态
---4.1 父子对象 均实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(依然有遮挡问题,跨层级拖拽正常)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEP : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnBeginDrag&#34;);
}
public void OnDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnDrag&#34;);
}
public void OnDrop(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnDrop&#34;+&#34; frmo:&#34;+eventData.pointerDrag.name);
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnEndDrag&#34;);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEC : MonoBehaviour,IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnBeginDrag&#34;);
}
public void OnDrag(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnDrag&#34;);
}
public void OnDrop(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnDrop&#34; + &#34; frmo:&#34; + eventData.pointerDrag.name);
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log(&#34;C:&#34; + gameObject.name + &#34; OnEndDrag&#34;);
}
}
panel_1内部
panel_1到panel_2
pane_1到panel_2中的Button
panel_1到自己内部的button
panel_1内部button到panel_1
button到重合部分
panel_1中的button到panel_2
panel_1中的button到panel_2中的button
panel_2到重合部分
重和部分到button_1
父子对象 均实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler时,注意遮挡问题,子对象对父对象的遮挡,重和部分的遮挡,以及onDrop一定先于endDrag调用。其他没有什么特别的地方
---4.2 仅父物体实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(这次谁也挡不住了)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEP : MonoBehaviour,IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler
{
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnBeginDrag&#34;);
}
public void OnDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnDrag&#34;);
}
public void OnDrop(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnDrop&#34;+&#34; frmo:&#34;+eventData.pointerDrag.name);
}
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log(&#34;P:&#34; + gameObject.name + &#34; OnEndDrag&#34;);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class TestIEC : MonoBehaviour
{
}
我们从panel_1中的button开始拖拽,拖拽到panel_2中的button上松开。可以看到父物体的Drag/Drop接口方法被调用
仅父物体实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler时,从子物体覆盖父物体的区域开始拖拽或结束拖拽,不论是Button或Image。父物体的接口方法都被正常调用
---4.3 重合时都实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(依然存在遮挡)
无论是从重合部分开始拖拽还是拖拽到重合部分,仅调用上层实现的方法
---4.4 重合时仅下层实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(依然存在遮挡)
无论上下层是button或Image,接口方法都无法被正常调用
结合4.2和4.4的一个坑:
重合时,下层实现所有Drag/Drop,上层什么都不实现,父物体实现所有Drag/Drop。从重合部分拖拽,调用父物体的Drag/Drop
---4.5 不完全实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(注意传递)
好吧直接写好了...
IBeginDragHandler可以不实现,依然正常调用IDragHandler
但如果反过来不实现IDragHandler,则无法正常调用IBeginDragHandler
想要正常调用IEndDragHandler, IDropHandler,则必须实现IDragHandler
<hr/>5.IPointerEnterHandler,IPointerExitHandler
emmmmmm
不详述了
父子均实现时表现正常
仅父物体实现,子物体不遮挡
仅实现IPointerExit可以被正常调用
<hr/>6.
这里其实还可以测一下
Button自身原有的click事件绑定器和IPointerClick事件接口同时使用时的情况
以及EventTrigger和UI事件接口同时使用时的情况
不过就算了吧,一来太占篇幅。二来根本没有意义...因为没有理由要同时使用两种及以上的事件触发方法
<hr/>
7.总结和应用
---7.1 总结一下
------7.1.1 唯一性、遮挡、搜索判断模式
唯一性:
在特定的交互行为出现时,UI事件的调用,无论是事件接口还是本身具有交互性的UI物体
事件的调用和传递具有唯一性
即特定交互行为只触发唯一一个物体所实现的事件接口方法,或是展现其对应的交互性(Button的点击,Scrollbar的拖拽)
由于唯一性的存在,UI事件的调用具有了遮挡问题
遮挡:
Point鼠标指针操作,Drag/Drop拖拽操作都存在遮挡问题:
同级别时,上层对下层的遮挡,即使上层物体没有实现接口,依然能够在重合部分阻挡下层实现的接口方法的调用
而跨父子物体时,对于父物体实现IPointer或是IDrag方法
普通的本身不具有交互性的组件像是 Image Text 它们不会在覆盖住父物体的部分阻挡父物体接口方法的调用
而具有交互性的组件,则会根据其本身具有的交互性,阻挡对应的父物体实现的UI事件接口方法被调用
比如上面提到的Button本身交互性需要鼠标的点击和抬起,但不需要鼠标的拖拽,因此,子物体Button能够阻挡父物体实现的Pointer鼠标指针事件接口方法被调用,但不会阻挡Drag/Drop事件接口的调用
而对于像Scrollbar(滑动条),这样的既需要点击交互,又有拖拽交互的组件,则会同时阻挡父物体的Pointer鼠标指针和Drag/Drop事件接口
我们还发现了有趣的,具有揭示性的一点:
我们添加了一个滑动条,并这样拖拽了一下
父物体Panel的IBeginDragHandler,IDragHandler,IEndDragHandler都被阻挡了,但居然调用了父物体Panel的OnDrop方法
其实不难猜到(尽管我没去翻过这部分被公开的源码),Unity的UI系统,那些本身具有交互性的组件,就是使用了这些UI事件接口来实现的交互性
搜索判断模式:
当出现交互时,是从最低一级的最上层的子物体开始依次到最高一级最上层的父物体,判断调用事件接口方法的过程中,只跨父子层级寻找,同父子层级只看最上层的物体有没有对应的方法
坑点1.重合时,下层和父物体实现,上层不实现不具有对应交互性需求,重合部分会调用到父物体的接口方法,而不是下层。因为发现上层没有就会跨父子层级寻找被实现的接口,而不是在同父子层级下依次寻找不同层
RectTransform.SetAsLastSibling(); (你懂的,这个方法就很有用了...)
坑点2.因为跨父子层级寻找,导致同父子层级时,上层不实现一定在重合部分阻挡下层实现的接口方法被调用
------7.1.2 注意事件的传递问题
只实现IPointerUpHandler根本无法正常调用,必须实现IPointerDownHandler,才能正常传递
IBeginDragHandler可以不实现,依然正常调用IDragHandler。但如果反过来不实现IDragHandler,则无法正常调用IBeginDragHandler。想要正常调用IEndDragHandler, IDropHandler,则必须实现IDragHandler
仅实现IPointerExit可以被正常调用
---7.2 一个应用
充分理解,才能更好的应用
在背包系统中我们希望实现一次性全部拾取和精确拾取
一次性全部拾取:
精确拾取:
我们将这个动态生成的物体,最高一级的父物体上的脚本中,通过Drag/Drop方法实现拖拽放入背包的操作
我们在其子物体上,用一个Button,添加一个脚本,通过IPointerDown方法告知父物体需要开启精确拾取设置面板,且从Button拖拽时不阻挡父物体的Drag/Drop方法,能够正常拾取
ok这个Button还可以设置一下Pressed Color 点击的时候变色来提示进行精确拾取
-----The End-----
/*
今天是在复习Unity的UI系统,翻到了以前写的背包系统,之前写的时候UI事件接口这里被坑了好久,虽然解决了问题,但没有进行系统的测试,也没有留下什么总结
今天测试了一下,发现好像没有那么简单,就赶紧来开文,一边测试一边记录总结,到现在写完这篇文章,自己也才总结清楚
嗯嗯,感觉酱紫挺不错的,印象更深刻了,以后忘记了还能回来看看这篇文章
ok复习大英复习大英,明天要考试了的说...
欢迎各路神仙能够留言指正,或是赐教一些我不知道的点,鄙人先写为敬
*/ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|