c调用按钮点击事件_Unity3d---对UI事件接口的一些测试和机制(坑)的总结

v2-0e50b42c759b605793adc30973135ef1_1440w.jpg?source=172ae18b

开文来记录一下自己摸索到的UI事件接口的一些运作机制(坑),就不赘述具体怎么使用这些事件接口了(不做教学...),本文主要是记自己发现的一些坑点

主要针对于在复杂的UI嵌套关系下,鼠标指针和拖拽操作的事件接口的调用关系,一些容易出错的地方进行记录

欢迎各路神仙留言指正,若能不吝赐教,鄙人先谢为敬

版本:Unity 2019.3.0

emmmmmm

怎么好像之前Unity2018的跨父对象层级的Drop方法还无法正常调用来着,做背包系统的时候真的坑死了,现在2019这个坑是被官方填了?


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组件


2.准备一下测试

场景物体摆放即父子关系

v2-a2cc3ab53fb11749a11de1a0c5770e20_b.jpg

分别给两个Panel添加一个名为 Test_IEP的脚本

v2-6dfc3cdef83b618b6dcd1eb82a742c1a_b.jpg

给所有 Button 和 Image 都添加一个名为 Test_IEC的脚本

v2-59707e849fc2a4a45d2dde1b1cd172c0_b.jpg

3.IPointer 鼠标点击事件接口测试

---3.1 父子均实现 IPointerDownHandler, IPointerUpHandler (出现覆盖问题)

using 
using 

------3.1.1 分别点击Panel没有被子对象覆盖的区域,和子对象

v2-047af49f301c05a3c4f6d1472cc78a0e_b.jpg
我们点击Panel没有被覆盖子对象的部分和子对象

在父子均实现IPointerDownHandler, IPointerUpHandler时,点击子对象覆盖住父对象的范围内时,只触发子对象的接口方法

------3.1.2 同级别下有遮挡时点击重和部分

v2-d3f057cefd5b02c361521cd9dc1499f7_b.jpg

同级别均实现IPointerDownHandler, IPointerUpHandler,UI物体有重和时,点击重和部分,仅触发最上层被点击到的UI对象的接口方法

补充一下:Hierarchy菜单,UI物体中越靠下的物体,才是越接近最上层的

---3.2 仅父对象实现 IPointerDownHandler, IPointerUpHandler (按钮是个特例)

using 
using 

v2-125a18b20ebcfe7e51920f85276b49fe_b.jpg
点击按钮,父对象的接口方法没有被触发

v2-5b6826ea502763ab8e81d5468d8666f8_b.jpg
但点击Image正常触发父对象的接口方法

仅父对象实现 IPointerDownHandler, IPointerUpHandler 时,点击子对象中的按钮仍无法触发父对象的接口方法(可以理解为按钮组件本身就有IPointer接口)依然覆盖掉了点击向父对象的传递。但点击到子对象中Image的一类本身不具有交互性的子对象,可以正常触发父对象的接口方法

v2-4641ac4a3b81bd79d88c831e2843272f_b.jpg
我们取消按钮的可交互性,点击按钮依然无法触发父对象的接口方法

v2-56d9b7429fc0d14bf15c16582499b63a_b.jpg
将按钮组件UnEnable时,点击按钮能够触发父对象的方法

---3.3 重合时仅下层实现 IPointerDownHandler, IPointerUpHandler (遮挡依然存在)

不论下层是否是按钮还是Image,点击重合部分,下层物体实现的接口方法都不会被触发

自然上下层都实现肯定只调用上层实现的接口方法

---3.4 只实现IPointerUpHandler时(无法正常被调用)

只实现IPointerUpHandler根本无法正常调用,必须实现IPointerDownHandler,才能正常传递,调用IPointerUpHandler的接口方法

---3.5 IPointerClickHandler(与3.1,3.2出现的情况类似)

参见3.1 和 3.2 测试结果类似,出现子对象覆盖,同级只触发最上层,以及按钮的特例覆盖效应


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 
using 

v2-1f22f68d64b2c9d9ee4fc3507ef311fb_b.jpg
panel_1内部

v2-707e1acf88e2d2d9909df97b81a8d245_b.jpg
panel_1到panel_2

v2-dd87557003b6a0771b9ac74d51cd1259_b.jpg
pane_1到panel_2中的Button

v2-923f6fc3f64f35eaf798c7d39bfc8c3d_b.jpg
panel_1到自己内部的button

v2-c5fe0afea665f26168ec00917c983843_b.jpg
panel_1内部button到panel_1

v2-dec0e4dd4d26f70086f0821ba052baef_b.jpg
button到重合部分

v2-e95d29e96704849ce9f9dda813158e93_b.jpg
panel_1中的button到panel_2

v2-d508c51fb62d98d62612b55b1be5f8e7_b.jpg
panel_1中的button到panel_2中的button

v2-05a2dae81c9a2b148f0f1001a48eb589_b.jpg
panel_2到重合部分

v2-a42a5e739dd9c1be6724dc749bd82cb5_b.jpg
重和部分到button_1

父子对象 均实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler时,注意遮挡问题,子对象对父对象的遮挡,重和部分的遮挡,以及onDrop一定先于endDrag调用。其他没有什么特别的地方

---4.2 仅父物体实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(这次谁也挡不住了)

using 
using 

v2-da2226686b106e5b5660be4ffa683b3c_b.jpg
我们从panel_1中的button开始拖拽,拖拽到panel_2中的button上松开。可以看到父物体的Drag/Drop接口方法被调用

仅父物体实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler时,从子物体覆盖父物体的区域开始拖拽或结束拖拽,不论是Button或Image。父物体的接口方法都被正常调用

---4.3 重合时都实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(依然存在遮挡)

v2-e2fceddb1bc41df259933d32bb75e1d8_b.jpg

v2-1ee41215d211235feb3c97d8437e5e3c_b.jpg

无论是从重合部分开始拖拽还是拖拽到重合部分,仅调用上层实现的方法

---4.4 重合时仅下层实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(依然存在遮挡)

无论上下层是button或Image,接口方法都无法被正常调用

v2-1322b9642123697d5809d7a14b464128_b.jpg

v2-afa9c39534c7082a0b3c004ddfb615c9_b.jpg

结合4.2和4.4的一个坑:

重合时,下层实现所有Drag/Drop,上层什么都不实现,父物体实现所有Drag/Drop。从重合部分拖拽,调用父物体的Drag/Drop

---4.5 不完全实现IBeginDragHandler,IDragHandler,IEndDragHandler,IDropHandler(注意传递)

好吧直接写好了...

IBeginDragHandler可以不实现,依然正常调用IDragHandler

但如果反过来不实现IDragHandler,则无法正常调用IBeginDragHandler

想要正常调用IEndDragHandler, IDropHandler,则必须实现IDragHandler


5.IPointerEnterHandler,IPointerExitHandler

emmmmmm

不详述了

v2-799f5d5e57ce1367daa0c2fe0e428b8d_b.jpg
父子均实现时表现正常

v2-86e5a40969862de0df94583904477ba1_b.jpg
仅父物体实现,子物体不遮挡

v2-85c86779d486d49214188fa915f50509_b.jpg
仅实现IPointerExit可以被正常调用

6.

这里其实还可以测一下

Button自身原有的click事件绑定器和IPointerClick事件接口同时使用时的情况

以及EventTrigger和UI事件接口同时使用时的情况

不过就算了吧,一来太占篇幅。二来根本没有意义...因为没有理由要同时使用两种及以上的事件触发方法


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事件接口

我们还发现了有趣的,具有揭示性的一点:

v2-b12b032225222a1a89f28426f7d39f78_b.jpg
我们添加了一个滑动条,并这样拖拽了一下

父物体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 一个应用

充分理解,才能更好的应用

在背包系统中我们希望实现一次性全部拾取精确拾取

一次性全部拾取:

v2-476f32d004f323a719fc3843b6c24540_b.jpg

v2-f669068643eeacd4d7862371ae149abe_b.jpg

精确拾取:

v2-a9ff7ddca1f18a998a4e37a29e14e275_b.jpg

v2-0f93c50754d7afbec3cbdf556fdb1f12_b.jpg

v2-fd77de1457c5274100447c87ff320d0b_b.jpg

我们将这个动态生成的物体,最高一级的父物体上的脚本中,通过Drag/Drop方法实现拖拽放入背包的操作

v2-7bc210939dc7a7f02f0a7ab90a274345_b.jpg

v2-bc229c4c44ed1c03c0331e79fe8feee8_b.jpg

我们在其子物体上,用一个Button,添加一个脚本,通过IPointerDown方法告知父物体需要开启精确拾取设置面板,且从Button拖拽时不阻挡父物体的Drag/Drop方法,能够正常拾取

v2-2d368434d83b59a7f4b30e1c5635565c_b.jpg

v2-e3da87ce420c673c59bbf3b4f6eb0766_b.jpg

v2-390f5e831aee66a727897fd45742fda4_b.jpg

v2-99a068b53fad06168c520b2e0487d1ff_b.jpg

ok这个Button还可以设置一下Pressed Color 点击的时候变色来提示进行精确拾取

v2-9ff7b3e9fe1c250b624b532a18b0bd88_b.jpg

-----The End-----

/*

今天是在复习Unity的UI系统,翻到了以前写的背包系统,之前写的时候UI事件接口这里被坑了好久,虽然解决了问题,但没有进行系统的测试,也没有留下什么总结

今天测试了一下,发现好像没有那么简单,就赶紧来开文,一边测试一边记录总结,到现在写完这篇文章,自己也才总结清楚

嗯嗯,感觉酱紫挺不错的,印象更深刻了,以后忘记了还能回来看看这篇文章

ok复习大英复习大英,明天要考试了的说...

欢迎各路神仙能够留言指正,或是赐教一些我不知道的点,鄙人先写为敬

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity3D中,uGUI是一个用户界面系统,它可以让开发者创建和管理游戏中的UI元素,如按钮、文本等。在uGUI中,世界坐标是指相对于场景原点的坐标系,而屏幕坐标是指相对于屏幕的坐标系。 在实际运用中,我们可以通过以下方式将世界坐标转换为屏幕坐标: ```csharp Vector3 worldPosition = new Vector3(10, 5, 0); Vector3 screenPosition = Camera.main.WorldToScreenPoint(worldPosition); ``` 这里我们使用了Camera.main.WorldToScreenPoint()方法,将世界坐标转换为屏幕坐标。这个方法需要指定一个摄像机,它将根据该摄像机的位置和朝向来计算屏幕坐标。在这个例子中,我们使用的是场景中的主摄像机,也就是Camera.main。 同样地,我们也可以将屏幕坐标转换为世界坐标: ```csharp Vector3 screenPosition = new Vector3(100, 100, 0); Vector3 worldPosition = Camera.main.ScreenToWorldPoint(screenPosition); ``` 这里我们使用了Camera.main.ScreenToWorldPoint()方法,将屏幕坐标转换为世界坐标。同样地,这个方法也需要指定一个摄像机。 在实际开发中,我们可以使用这些方法来处理鼠标点击UI元素的位置调整等操作。例如,我们可以通过以下代码来将一个UI元素移动到鼠标点击的位置: ```csharp public void OnPointerClick(PointerEventData eventData) { Vector3 worldPosition = Camera.main.ScreenToWorldPoint(eventData.position); transform.position = worldPosition; } ``` 这里我们使用了Unity3D中的事件系统,当鼠标点击时,OnPointerClick()方法会被调用。在这个方法中,我们通过Camera.main.ScreenToWorldPoint()方法将屏幕坐标转换为世界坐标,然后将UI元素的位置设置为该世界坐标。这样,当我们点击鼠标时,UI元素就会移动到鼠标点击的位置。 总之,在uGUI中,世界坐标和屏幕坐标的转换是非常重要的,它们可以帮助我们处理一些常见的UI操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值