文章目录
- 1.前言
- 2.综述
-
- 2.1 BaseInputModule
- 2.2 PointerInputModule
-
- 2.2.1 PointerData字典
- 2.2.2 获取Touch事件PointerData
- 2.2.3 获取Mouse事件的数据
- 2.2.4 其他方法
- 2.3 StandaloneInputModule
-
- 2.3.1 Update/Move/Submit事件
- 2.3.2 常规事件处理
- 3.结语
1.前言
在上一篇中梳理了一下整个事件系统的流程,包括Workflow,此文则详细讲解一下InputModule本身,并对一些方法做一下解释。
2.综述
InputModule的结构如下所示:
BaseInputModule为原始基类,只包含最基本的功能。PointerInputModule主要功能是获取当前touch或者鼠标位置坐标、状态以及对应游戏物体信息。最终Standalone/TouchInputModule则进行事件判断与处理。当然页包括一些VR sdk中自定义的InputModule。
2.1 BaseInputModule
此类包含基本的功能包括模块启动管理、获取当前input模块、处理Enter/Exit以及其他辅助功能:
1)启动管理
即在OnEnable和OnDisable方法中处理的事件。即将此模块添加到添加到EventSystem的inputModule列表中。
2)获取当前Input模块
此input模块是指最基本的输入模块,基本上就是unity最基本的Input类的简单封装。用户可以自定义Input类,但是目前基本没有此类需求。
3)处理Enter/Exit
此功能对应代码如下所示,是比较重要的一个方法,但是也是最让人费解的方法。即使有注释也比较费解为什么要这么处理。如果只是处理Enter和Exit方法,则此段代码从 “if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget) return;”开始即可,后来从此方法的使用知道,最开始的一段代码是为了一些特定功能添加的。
// walk up the tree till a common root between the last entered and the current entered is foung// send exit events up to (but not inluding) the common root. Then send enter events up to// (but not including the common root).protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget){
// if we have no target / pointerEnter has been deleted// just send exit events to anything we are tracking// then exitif (newEnterTarget == null || currentPointerData.pointerEnter == null){
for (var i = 0; i < currentPointerData.hovered.Count; ++i)ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);currentPointerData.hovered.Clear();if (newEnterTarget == null){
currentPointerData.pointerEnter = null;return;}}// if we have not changed hover targetif (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget)return;GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);// and we already an entered object from last timeif (currentPointerData.pointerEnter != null){
// send exit handler call to all elements in the chain// until we reach the new target, or null!Transform t = currentPointerData.pointerEnter.transform;while (t != null){
// if we reach the common root break out!if (commonRoot != null && commonRoot.transform == t)break;ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);currentPointerData.hovered.Remove(t.gameObject);t = t.parent;}}// now issue the enter call up to but not including the common rootcurrentPointerData.pointerEnter = newEnterTarget;if (newEnterTarget != null){
Transform t = newEnterTarget.transform;while (t != null && t.gameObject != commonRoot){
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);currentPointerData.hovered.Add(t.gameObject);t = t.parent;}}}
4)其他
其他均为辅助方法,但是有个方法可能以后会用到,即寻找两个游戏物体的公共节点,如下
protected static GameObject FindCommonRoot(GameObject g1, GameObject g2){
if (g1 == null || g2 == null)return null;var t1 = g1.transform;while (t1 != null){
var t2 = g2.transform;while (t2 != null){
if (t1 == t2)return t1.gameObject;t2 = t2.parent;}t1 = t1.parent;}return null;}
2.2 PointerInputModule
此类继承BaseInputModule模块,作用很简单即获取当前touch或者鼠标的点信息,但是处理确立逻辑却比较繁琐,这是因为涉及到鼠标事件的左中右三个按键问题。此模块通过维护m_PointerData列表来处理各种功能。如下所示。
2.2.1 PointerData字典
通过一个问题来解释这个字典的作用。如何判断鼠标滑动时的delta?解决此问题需要把上一帧的鼠标位置记录下来,然后用当前帧鼠标的坐标去减上一帧的值。PointerData字典就是此作用,将当前帧Pointer信息记录下来,只是此字典记录的不止一个点。包括鼠标左右中三个按键(对应key值为-1,-2,-3)、虚拟按键值(key为-4)以及touch(key为touch的touchID)。
2.2.2 获取Touch事件PointerData
TouchPointerData的获取比较简单。即获取到当前touch数据,针对不同手指不同id去处理,但是大部分只有一个手指,获取当前点击状态。然后射线检测,获取当前检测的游戏物体。
代码如下:
protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released){
PointerEventData pointerData;var created = GetPointerData(input.fingerId, out pointerData, true);pointerData.Reset();pressed = created || (input.phase == TouchPhase.Began);released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended);if (created)pointerData.position = input.position;if (pressed)pointerData.delta = Vector2.zero;elsepointerData.delta = input.position - pointerData.position;pointerData.position = input.position;pointerData.button = PointerEventData.InputButton.Left;if (input.phase == TouchPhase.Canceled){
pointerData.pointerCurrentRaycast = new RaycastResult();}else{
eventSystem.RaycastAll(pointerData, m_RaycastResultCache);var raycast = FindFirstRaycast(m_RaycastResultCache);pointerData.pointerCurrentRaycast = raycast;m_RaycastResultCache.Clear();}return pointerData;}
2.2.3 获取Mouse事件的数据
此部分比较麻烦,因为鼠标事件包含左右中三个,但是他们的位置以及射线检测到的游戏物体是相同的,唯一不同的是点击状态不同。所以针对这些问题定义了三个类:
1)MouseButtonEventData
此类只是在PointerData的基础上进行封装,增加了点击状态处理,即是否进行了点击。鼠标左右中三个按键都是由MouseButtonEventData表现。
2)ButtonState
此类是在MouseButtonEventData基础上的进一步封装。添加了按键位置参数,即表示此点击位置是左右中哪个按键进行了点击。个人认为可以完全跟上一个类合并。
3)MouseState
此类是在ButtonState的基础上继续进行的封装。通过m_TrackedButtons列表来维护三个按键的信息,所以列表中中最多只有三个元素,且除了刚开始运行时,有且只有三个元素。
所以获取mouse事件的数据时,返回值是一个MouseState,流程与Touch相同,只是增加了Copy数据的工程。因为左中右三个按键的位置与对应的游戏物体相同。
2.2.4 其他方法
还定义了其他一些方法,比如processMove、ProcessDrag等方法,其实是比较简单的。但是需要注意的一点是PointerEventData存储的数据包含上一帧的信息,所以处理ProcessMove时,只需要传入PointerEventData就可以。等处理结束后,下一帧未开始的时候一些数据才会统一,比如PointerEnter才会与当前射线检测的物体统一(此时不一定相同,因为接收PointerEnter的游戏物体有可能是当前游戏物体的父物体)。
2.3 StandaloneInputModule
此类才是真正去处理事件的类,是由Process处理的,首先处理Update/Move/Submit事件,然后处理ProcessTouchEvent和ProcessMouseEvent事件,而他们中的核心方法是ProcessTouchPress和ProcessMousePress。
2.3.1 Update/Move/Submit事件
这些事件比较处理比较简单,只是对当前选择是游戏物体,发送update、move以及submit事件。这些事件是pc端的事件,对应一些特殊的按键,比如Enter、esc以及ased字母键。
2.3.2 常规事件处理
常规事件是指常用到的事件比如drag、pointerEnter/exit以及click等事件,移动端和pc端分别是由ProcessTouchEvent和ProcessMouseEvent分别处理。但是不管是Touch还是Mouse,都是按照Press、Move和Drag进行处理。这里的Move与2.3.1中的move事件不同。此处的Move是处理PointerEnter/Exit事件,同理Press处理PointerDown、PointerClick以及PointerUp事件。
ProcessTouchEvent
此方法处理Touch事件,由于移动端不止一个手指触摸,所以所有的手指都要处理。思路是这样的:
(一)首先,如果在当前帧Pressed即手机触碰到触摸板,则处理PointerDown事件,同时记录当前游戏物体可能的Drag对象。
(二)如果当前帧没有Released,则根据上述的结果去处理Move和Drag(如果拖动距离大于EventSystem规定的距离才去执行)。Move处理的是PointerEnter/Exit事件,所以移动端Touch和pc端Mouse处理方式有差异。
(三)如果当前帧Released(即手离开屏幕)则处理PointerUp事件(如果之前Pressed状态时如果没有处理PointerDown事件,且无click事件,则不会处理PointerUp事件),同时处理EndDrag和Drop事件(如果条件允许)。
此处存在一个问题,即不会单独处理PointerUp事件。不管是Mouse还是Touch,如果一个游戏物体既没有down事件也没有click事件,是不会触发PointerUp事件的。
ProcessMouseEvent
由于mouse事件不存在鼠标离开的问题,所以会一直处理ProcessMove。而且由于鼠标有左右中三个按键,所以针对Press和Drag都会进行判断。Press的处理流程与Touch基本相同(如上文所示)。区别则有两点,一个是touch按下时会判断是否处理Enter/Exit事件;另一个则是鼠标Release时,mouse会判断是否处理Enter/Exit事件。
3.结语
以上是InputModule模块整个的事件处理流程。