赞
踩
在上文EventSystem中,EventSystem调用了BaseInputModule的process函数,将输入的部分交给了输入模块去处理。根据处理流程的不一样,开发者可以实现不同的BaseInputModule子类,并重写process函数。Unity则是提供了默认的StandaloneInputModule类。这一篇文章先来了解BaseInputModule的继承结构,随后以process函数为入口,继续了解事件系统。
BaseInputModule抽象类
PointerInputModule抽象类 继承BaseInputModule
StandaloneInputModule类继承PointerInputModule
TouchInputModule类 继承PointerInputModule(已废弃 功能完全整合到了StandaloneInputModule)
上篇EventSytem 有说到 其主要是更新输入模块BaseInputModule 的process 函数 所以这个为输入响应入口
// 用来缓存射线投射后的取得的结果
[NonSerialized]
protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>();
//轴向相关的数据
private AxisEventData m_AxisEventData;
private EventSystem m_EventSystem;
private BaseEventData m_BaseEventData;
AxisEventData 的定义也比较简单 一个原始的输入方向 移动朝向的美剧
其他
/// <summary> /// Event Data associated with Axis Events (Controller / Keyboard). /// </summary> public class AxisEventData : BaseEventData { /// <summary> /// Raw input vector associated with this event. /// </summary> public Vector2 moveVector { get; set; } /// <summary> /// MoveDirection for this event. /// </summary> public MoveDirection moveDir { get; set; } public AxisEventData(EventSystem eventSystem) : base(eventSystem) { moveVector = Vector2.zero; moveDir = MoveDirection.None;
通过输入参数确定轴向数据 (移动朝向 和方向枚举)
/// <param name="deadZone">Dead zone.</param>
protected virtual AxisEventData GetAxisEventData(float x, float y, float moveDeadZone)
{
if (m_AxisEventData == null)
m_AxisEventData = new AxisEventData(eventSystem);
m_AxisEventData.Reset();
m_AxisEventData.moveVector = new Vector2(x, y);
m_AxisEventData.moveDir = DetermineMoveDirection(x, y, moveDeadZone);
return m_AxisEventData;
}
实际上在输入模块启用的时候 是添加了一个BaseInput 脚本 ,这个脚本实际上是对以前常用的InputSystem的封装 。所有的输入触摸都是通过这里做检测。
/// <summary> /// The current BaseInput being used by the input module. /// </summary public BaseInput input { get { if (m_InputOverride != null) return m_InputOverride; if (m_DefaultInput == null) { var inputs = GetComponents<BaseInput>(); foreach (var baseInput in inputs) { // We dont want to use any classes that derrive from BaseInput for default. if (baseInput != null && baseInput.GetType() == typeof(BaseInput)) { m_DefaultInput = baseInput; break; } } if (m_DefaultInput == null) m_DefaultInput = gameObject.AddComponent<BaseInput>(); } return m_DefaultInput; } }
ventSystem调用Process接口处理输入以及事件。由子类去实现这个方法。
/// <summary>
/// Process the current tick for the module.
/// </summary>
public abstract void Process();
BaseInputModule的每帧更新函数,由EventSystem驱动。
/// <summary>
/// Update the internal state of the Module.
/// </summary>
public virtual void UpdateModule()
{}
PointerInputModule定义了一些点操作的变量以及方法。
m_PointerData用来记录当前点的数据,每有一个Touch输入,那么会缓存在m_PointerData记录中。
protected Dictionary<int, PointerEventData> m_PointerData = new Dictionary<int, PointerEventData>(); /// <summary> /// Search the cache for currently active pointers, return true if found. /// </summary> /// <param name="id">Touch ID</param> /// <param name="data">Found data</param> /// <param name="create">If not found should it be created</param> /// <returns>True if pointer is found.</returns> protected bool GetPointerData(int id, out PointerEventData data, bool create) { if (!m_PointerData.TryGetValue(id, out data) && create) { data = new PointerEventData(eventSystem) { pointerId = id, }; m_PointerData.Add(id, data); return true; } return false; }
这个是处理了触摸操作
/// Given a touch populate the PointerEventData and return if we are pressed or released. /// </summary> /// <param name="input">Touch being processed</param> /// <param name="pressed">Are we pressed this frame</param> /// <param name="released">Are we released this frame</param> /// <returns></returns> 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; else pointerData.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; }
GetMousePointerEventData()函数是处理鼠标输入 这里是个虚方法是支持重写
StandaloneInputModule是标准输入模块。鼠标输入和触摸出入都在这里管理
输入模块最终要的函数Process()
public override void Process() { if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus()) return; bool usedEvent = SendUpdateEventToSelectedObject(); // case 1004066 - touch / mouse events should be processed before navigation events in case // they change the current selected gameobject and the submit button is a touch / mouse button. // touch needs to take precedence because of the mouse emulation layer if (!ProcessTouchEvents() && input.mousePresent) ProcessMouseEvent(); if (eventSystem.sendNavigationEvents) { if (!usedEvent) usedEvent |= SendMoveEventToSelectedObject(); if (!usedEvent) SendSubmitEventToSelectedObject(); } }
首先检查当前应用是否是focus状态,如果是非focus状态,且在该状态下是忽略事件处理的,那么跳过处理。
SelectedObject代表EventSystem当前记录的选中的UI。SendXXSelectedObject表示,是否要对当前的选中UI发送Update/Move/Submit/Cancel事件。Process会首先尝试执行update事件,同时记录usedEvent。usedEvent代表是否已经执行过事件,如果执行过,后续不执行,也就是同一帧里仅执行一次事件。如果未执行过事件且sendNavigationEvents,那么尝试继续处理Move事件以及Submit/Cancel事件。
sendNavigationEvents代表什么呢?主机游戏常用手柄操作,PC游戏常用键盘。它们都有上下左右,以及确认、取消,常用的六个按钮。sendNavigationEvents表示当按下手柄的方向键的时候,会尝试调用被选中UI的Move事件。当前选中UI会deselect,并且在当前选中UI的某一个方向上的UI会被Select选中。在游戏中这是非常常见的,比如在设置面板中有很多玩家可设置的选项,方向键就可以用来在这些选项中进行切换。
Move: 当按下上下左右方向键的时候,会根据当前SelectableObject的位置,查找对应方向的下一个SelectableObject,并Move至对应SelectableObject
Submit、Cancel: 对当前SelectableObject发送对应事件,例如当前SelectableObject是一个Button,那么按下Submit事件,就会触发Button的OnSubmit接口。
如果没有触发以上事件的话,那么Process会尝试处理鼠标或者触摸事件。代码是首先去处理Touch事件,如果没有点击事件,且存在鼠标,那么去处理鼠标事件。
处理触摸Touch事件
private bool ProcessTouchEvents() { for (int i = 0; i < input.touchCount; ++i) { Touch touch = input.GetTouch(i); if (touch.type == TouchType.Indirect) continue; bool released; bool pressed; //获取手指状态存储到pointer里 var pointer = GetTouchPointerEventData(touch, out pressed, out released); ProcessTouchPress(pointer, pressed, released); if (!released) { ProcessMove(pointer); ProcessDrag(pointer); } else RemovePointerData(pointer); } return input.touchCount > 0; }
这里是遍历每根手指的Touch示例,每一个都能得到一个PointerEventData ,这个包含了手指触摸后的很多信息 ,包括位置,拖动信息等等。同时根据手指按下抬起能获取到三个状态 从而可以处理 第一帧按下 中间拖动 最后一帧抬起的事件处理。
protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released) { PointerEventData pointerData; //获取缓存的输入信息,没有就新创建(代表手指刚触摸) 参考上面PointerInputModule的方法 var created = GetPointerData(input.fingerId, out pointerData, true); pointerData.Reset(); //这里判断是否手指刚按下(true 按下 false 不是刚刚下) pressed = created || (input.phase == TouchPhase.Began); //这里判断手指是否抬起释放 released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended); //如果是第一次创建 保存输入位置信息 if (created) pointerData.position = input.position; //如果是刚按下 位置移动偏移初始化为(0,0) if (pressed) pointerData.delta = Vector2.zero; else //不是刚按下(其他滑动或者抬起状态)就一直记录偏移量 pointerData.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; }
在这里要说明下TouchPhase 相关的内容 ,以前没有UGUI的时候 我们自己做触摸相关的都是通过这个枚举判断
// // 摘要: // Describes phase of a finger touch. public enum TouchPhase { // // 摘要: // A finger touched the screen. Began = 0, // // 摘要: // A finger moved on the screen. Moved = 1, // // 摘要: // A finger is touching the screen but hasn't moved. Stationary = 2, // // 摘要: // A finger was lifted from the screen. This is the final phase of a touch. Ended = 3, // // 摘要: // The system cancelled tracking for the touch. Canceled = 4 }
通过上面函数 就拿到了 三个状态 我们就可以处理4个事件
//press事件 和 release事件 刚好就是第一帧和最后一帧
protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
这个函数中要注意几行代码
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
send exit events as we need to simulate this on touch up on touch device
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
上面代码是通过当前对象沿着Hierarchy一层层往上找实现了对应 事件接口的对象 同时发布事件
相似的还有 ExecuteEvents.Execute 只再当前对象上发布事件 事件相关系统 后面再讲
//手指移动事件
/// <summary>
/// Process movement for the current frame with the given pointer event.
/// </summary>
protected virtual void ProcessMove(PointerEventData pointerEvent)
{
var targetGO = (Cursor.lockState == CursorLockMode.Locked ? null :
pointerEvent.pointerCurrentRaycast.gameObject);
HandlePointerExitAndEnter(pointerEvent, targetGO);
}
在手指按下移动过程中 我们可能会进入某些对象的Enter handler 离开某些Exit haandler
参数列表的第一个参数PointerData会记录之前的enter的GameObject(oldEnterObject),第二个参数则是新enter的GameObject(newEnterTarget)。
//拖拽事件 /// <summary> /// Process the drag for the current frame with the given pointer event. /// </summary> protected virtual void ProcessDrag(PointerEventData pointerEvent) { if (!pointerEvent.IsPointerMoving() || Cursor.lockState == CursorLockMode.Locked || pointerEvent.pointerDrag == null) return; if (!pointerEvent.dragging && ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position, eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold)) { ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler); pointerEvent.dragging = true; } // Drag notification if (pointerEvent.dragging) { // Before doing drag we should cancel any pointer down state // And clear selection! if (pointerEvent.pointerPress != pointerEvent.pointerDrag) { ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler); pointerEvent.eligibleForClick = false; pointerEvent.pointerPress = null; pointerEvent.rawPointerPress = null; } ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.dragHandler);
这段代码也比较清晰 就是主要处理拖拽的状态 处理拖拽的事件。
整体来说 触摸事件 结构写的还是蛮清晰。 主要是理解下 其中的一些手势判定,和对应的数据存储PointerData
鼠标操作类似 也是相关状态 (目前已经合并到标准输入模块中了 ,所以基本是无感)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。