UGUI的事件系统
UGUI的事件系统是用户和UGUI交互的基石
主要处理用户的输入并传递给UI元素
键盘和手柄 鼠标和触摸 事件系统会处理用户的这些输入 并把对应的事件传递给相应的UI元素
例如按钮的单击事件 ScrollView的滚动事件 Slider的滑块滑动等
参考文献:
https://zhuanlan.zhihu.com/p/360466110
https://zhuanlan.zhihu.com/p/111082394
https://docs.unity3d.com/Packages/[email protected]/manual/EventSystem.html
UGUI的事件是如何运行的?
首先 UGUI自己定义了一套事件的规范 这套事件定义是纯C#的 也就是说不仅是UGUI可以用
你自己写的游戏系统也可以用这套事件规范
然后来说说这两个组件
EventSystem组件
一个场景里只能有一个 负责处理输入和射线 然后转发事件
就是这个组件在驱动着事件系统的运行
它的几个配置项
First Selected 默认选择的物体?这个一开始我其实没明白
后来问了GPT才懂 例如说游戏开局时 会展示一个用户名输入框
你不希望用户还要点一下输入框然后唤醒输入法 而是一开始就把焦点聚焦在这个输入框身上
就可以把这一项设置为输入框
Send Navigation Events 键盘的方向键起导航事件控制的作用
例如说你可以用方向键来控制焦点在几个按钮间切换 按下空格执行按钮的逻辑
(典型的RPG Maker式的纯键盘控制方式 如果你玩过用它做的游戏一定懂我在说什么)
前段时间我写了个很笨的工具函数 把场景中所有按钮的Navigation改成none来避免导航事件
事实上这是很笨的方法 你只要取消勾选这一项就可以了
Drag Threshold 视为拖拽操作的最小距离
字面意思 如果你希望拖拽操作更灵敏或者更不灵敏就可以修改这里
所以EventSystem具体做了什么呢?
它每一帧都会去调用下面这个模块 Standalong Input Module的Process方法 驱动事件的生成和处理
你可以理解为UGUI的事件Update就是跑在EventSystem里的
Standalong Input Module
生成事件 传递事件 你可以理解为一个中介
它和Input System实际上关系不大 是两个不同的模块
当你点击按钮时这两个模块都能对你点击这件事产生反应
只不过一个是依赖硬件输入(鼠标真的被按下了这个信号)另一个是依赖于按下后产生的事件传递引起反应
然后我们提一下几个属性
Horizontal Axis和Vertical Axis 这两个属性基本上是导航功能在用的
水平轴和垂直轴的输入 默认值Horizontal和Vertical代表键盘或者摇杆的X Y输入
假如说Horizontal Axis你改成Mouse X了
那你在不按下鼠标左键的情况下移动鼠标 就会改变导航的焦点UI元素
Submit Button和Cancel Button确认按钮和撤销按钮的输入指定
例如默认值Submit和Cancel
Submit对应的是键盘的Enter和手柄的A
Cancel对应的是键盘的ESC和手柄的B
你只要改成Space就能变成空格
Input Actions Per Second 每秒轮询几次输入 用来改变响应的灵敏度的 默认一秒轮询10次 来确保输入能被捕捉到
Repeat Delay 当你持续按下同一个键的时候 延迟多久才视作是重复触发逻辑 这里的单位是0.5s
例如当你连续按压空格键超过0.5s才视作连续跳跃 重复跳跃逻辑
两者的关系?发生了什么?
我们在EventSystem提到过 每一帧Standalong Input Module的Process方法都回被调用
EventSystem驱动着Standalong Input Module去生成和处理事件
我们举一个详细的例子 鼠标点击了一个按钮
首先EventSystem的Update调用了Standalong Input Module的Process
Process发现了鼠标点击的行为 这里才是真正处理输入行为的地方
首先Standalong Input Module用射线去确定鼠标点击的元素是哪一个
它会去找Canvas渲染的UI元素里有谁是可以作为射线碰撞的对象的
(这一部分之后还会讲的更清楚)
然后尝试去获取基类IPointerClickHandler 发现这个物体确实有这个基类
因为Button是继承这个基类的 所以去执行了鼠标点击事件应该执行的对应逻辑
也就是onClick对应的事件逻辑
UI的射线系统
Graphic Raycaster – Used for UI elements, lives on a Canvas and searches within the canvas
Physics 2D Raycaster – Used for 2D physics elements
Physics Raycaster – Used for 3D physics elements
一般情况下第一种就满足我们的绝大部分需求了 二三种都是属于一种拓展
旨在利用UI事件来控制场景中的2D元素或3D元素
例如你想让场景中的物体有类似UI的功能(RTS里面鼠标可以控制的单位 就像一堆有着按钮逻辑的游戏对象一样)
就可以通过挂载这些Raycaster来实现接受射线执行逻辑的效果
这里就要提到我们刚刚没讲完的部分了
你会发现几乎可以交互的UI元素都会自带RaycastTarget这一选项
当Standalong Input Module在试图用射线判断对象的时候
会经历一系列的排除法 我就讲几个主要的 实际要经过很多项排除
第一层 它得是一个Graphic 我们在上一篇文章说过了 UI元素基类都是Graphic 这就把非UI的元素刷掉了
第二层 它得在Canvas底下 对吧 你都不在画布底下我为什么要关心呢
第三层 就是RaycastTarget得为True
第四层 调用剩下的这些Graphic自身的Raycast 来判断谁先返回true 谁就是这次的对象
这个过程并不完整 省略了一些我认为不太需要去记的步骤 但一定要注意实际的判断过程更复杂
所以关闭不必要的RyacastTarget是很有用的 因为可以减少射线的计算量 而且也不会出现射线打到的物体不是自己想要的情况
事件的载体——EventData
Standalong Input Module在执行Process的时候会生成EventData
也就是文章开头我们说过的自定义的事件规范
EventData是用于事件系统的事件类(基类BaseEventData)
我们之后最常见到的PointerEventData便是所有触摸和点击事件的载体
UI的各种交互事件
事件系统支持很多事件 但是你会发现UGUI的Button里开放的接口只提供了一种onClick事件
实际需求中我们光用Click是远远不够的 需要划分的非常详细 按下 抬起 长按 等
那么我们要怎么才能实现这样的Button呢 没有Button组件也可以有Button的操作吗?
当然是可以的 事件系统提供了很多很多的接口 只要继承了这些接口且是继承Mono的元素
事件系统便会在UI遇到对应事件的时候调用接口的方法 实现对应的逻辑
https://docs.unity3d.com/Packages/[email protected]/manual/SupportedEvents.html
-
IPointerEnterHandler – OnPointerEnter – 当指针进入对象时调用
-
IPointerExitHandler – OnPointerExit – 当指针退出对象时调用
-
IPointerDownHandler – OnPointerDown – 当指针按下对象时调用
-
IPointerUpHandler – OnPointerUp – 释放指针时调用(在指针单击的游戏对象上调用)
-
IPointerClickHandler – OnPointerClick – 在同一对象上按下并释放指针时调用
-
IInitializePotentialDragHandler – OnInitializePotentialDrag – 找到拖动目标时调用,可用于初始化值
-
IBeginDragHandler – OnBeginDrag – 当拖动即将开始时在拖动对象上调用
-
IDragHandler – OnDrag – 发生拖动时在拖动对象上调用
-
IEndDragHandler – OnEndDrag – 拖动完成时在拖动对象上调用
-
IDropHandler – OnDrop – 在拖动完成的对象上调用
-
IScrollHandler – OnScroll – 鼠标滚轮滚动时调用
-
IUpdateSelectedHandler – OnUpdateSelected – 每个刻度在所选对象上调用
-
ISelectHandler – OnSelect – 当对象成为选定对象时调用
-
IDeselectHandler – OnDeselect – 在所选对象取消选择时调用
-
IMoveHandler – OnMove – 当移动事件发生时调用(左、右、上、下)
-
ISubmitHandler – OnSubmit – 按下提交按钮时调用
-
ICancelHandler – OnCancel – 按下取消按钮时调用
例如你现在想要一个满足上面需求的按钮
你便可以创建一个新的类继承Button
然后对PointerEventData做出不同的逻辑就可以实现了
你也有别的方法
例如添加一个EventTrigger组件 事件触发器
可以在不需要继承Button的情况下 指定事件触发器要做的条件和对应的执行逻辑
也可以实现一样的效果