赞
踩
从今天开始,我们进入事件系统的的最后一部分: 输入模块(InputModules).
输入模块是事件系统的核心, 是真正使用检测和调用回调的地方.
输入模块主要有几个角色, 分别为:
还有几个零散的类, 用到的时候再介绍.
BaseInput很简单, 首先它继承于UIBehavior, 其次是简单的封装Input模块函数. 它也没有面板属性, 纯粹是一个工具类.
它的作用很简单, 大部分是供输入模块查询鼠标或者触摸或者轴事件的一些基本信息, 本身并不维护数据, 只是一个中转站.
public class BaseInput : UIBehaviour { // 用户键入的当前 IME 组合字符串, 所谓IME也就是输入法, Unity提供内置的输入法, 也提供自定义输入法 public virtual string compositionString { get { return Input.compositionString; } } // 输入法模式: Auto(仅在选中文本字段时启用IME 输入(默认)), On(启用), Off(禁用) public virtual IMECompositionMode imeCompositionMode { get { return Input.imeCompositionMode; } set { Input.imeCompositionMode = value; } } // 输入法输入光标位置 public virtual Vector2 compositionCursorPos { get { return Input.compositionCursorPos; } set { Input.compositionCursorPos = value; } } // 是否检测到鼠标 public virtual bool mousePresent { get { return Input.mousePresent; } } // 鼠标点按的按下部分 public virtual bool GetMouseButtonDown(int button) { return Input.GetMouseButtonDown(button); } // 鼠标点按的弹起部分 public virtual bool GetMouseButtonUp(int button) { return Input.GetMouseButtonUp(button); } // 鼠标某个键一直按下(按住) public virtual bool GetMouseButton(int button) { return Input.GetMouseButton(button); } // 鼠标位置(屏幕坐标) public virtual Vector2 mousePosition { get { return Input.mousePosition; } } // 鼠标滚动量 public virtual Vector2 mouseScrollDelta { get { return Input.mouseScrollDelta; } } // 当前设备是否支持触摸 public virtual bool touchSupported { get { return Input.touchSupported; } } // 触摸次数 public virtual int touchCount { get { return Input.touchCount; } } // 根据索引获取触摸 public virtual Touch GetTouch(int index) { return Input.GetTouch(index); } // 获取原始的轴数据(未平滑) public virtual float GetAxisRaw(string axisName) { return Input.GetAxisRaw(axisName); } // 键盘的某个键点按的按下部分 public virtual bool GetButtonDown(string buttonName) { return Input.GetButtonDown(buttonName); } }
BaseInputModule是输入模块的祖先类, 也是一个抽象类, 不能实例化. 提供一些基本的字段, 属性和函数供子类使用.
它同时需要依赖EventSystem使用.
[RequireComponent(typeof(EventSystem))]
public abstract class BaseInputModule : UIBehaviour
{
// ...
}
BaseInputModule提供了一些简单的字段和属性.
/// 射线投射结果缓存列表, 避免频繁申请 [NonSerialized] protected List<RaycastResult> m_RaycastResultCache = new List<RaycastResult>(); /// 轴数据, 根据传入的封装事件数据 private AxisEventData m_AxisEventData; /// 引用EventSystem private EventSystem m_EventSystem; /// 基础的事件数据, 用于在一些地方进行初始化变量 private BaseEventData m_BaseEventData; /// 提供的BaseInput重载, 自定义的Input protected BaseInput m_InputOverride; /// 对象身上找到的第一个BaseInput private BaseInput m_DefaultInput; /// BaseInput属性 public BaseInput input { get { // 提供重载机制 if (m_InputOverride != null) return m_InputOverride; // 只使用BaseInput, 不使用子类 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; } } /// 引用的eventSystem属性 protected EventSystem eventSystem { get { return m_EventSystem; } }
默认情况下生成的EventSystem对象身上并没有BaseInput组件, 一般都是启动后动态添加.
前面的文章介绍过了, 输入模块在OnEnable和OnDisable时调用EventSystem的更新接口, 在内部将自己从EventSystem管理的输入模块列表中移除.
// BaseInputModule.cs protected override void OnEnable() { base.OnEnable(); m_EventSystem = GetComponent<EventSystem>(); m_EventSystem.UpdateModules(); } protected override void OnDisable() { m_EventSystem.UpdateModules(); base.OnDisable(); } // EventSystem.cs public void UpdateModules() { // 获取当前对象上所有的输入模块, 如果该模块处于非激活状态则移除 GetComponents(m_SystemInputModules); for (int i = m_SystemInputModules.Count - 1; i >= 0; i--) { if (m_SystemInputModules[i] && m_SystemInputModules[i].IsActive()) continue; m_SystemInputModules.RemoveAt(i); } }
BaseInputModule提供了一些抽象方法需子类具体实现, 并提供了一些通用的类方法.
// 抽象的事件处理方法, 需要具体子类实现 public abstract void Process(); // 查找第一个投射结果, 需要游戏对象存在, 如果没有则返回一个新建的投射结果 protected static RaycastResult FindFirstRaycast(List<RaycastResult> candidates) { for (var i = 0; i < candidates.Count; ++i) { if (candidates[i].gameObject == null) continue; return candidates[i]; } return new RaycastResult(); } // 根据坐标确定移动方向 protected static MoveDirection DetermineMoveDirection(float x, float y) { return DetermineMoveDirection(x, y, 0.6f); } // 根据坐标确定移动方向(上下左右, 未移动) // moveDeadZone用于最小位移判断 protected static MoveDirection DetermineMoveDirection(float x, float y, float deadZone) { // 忽略数值很小的移动 // if vector is too small... just return if (new Vector2(x, y).sqrMagnitude < deadZone * deadZone) return MoveDirection.None; if (Mathf.Abs(x) > Mathf.Abs(y)) { // 水平方向(左右) if (x > 0) return MoveDirection.Right; return MoveDirection.Left; } else { // 竖直方向(上下) if (y > 0) return MoveDirection.Up; return MoveDirection.Down; } } // 找出两个游戏对象共同的父节点 // 通过两重循环 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; }
BaseInputModule提供了一些查询方法供子类和外部使用.
// 根据传入的数据, 封装轴事件数据 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; } // 获取基础的事件数据, 用于在一些地方进行初始化变量 protected virtual BaseEventData GetBaseEventData() { if (m_BaseEventData == null) m_BaseEventData = new BaseEventData(eventSystem); m_BaseEventData.Reset(); return m_BaseEventData; } // 给定id的指针是否位于某个对象区域 public virtual bool IsPointerOverGameObject(int pointerId) { return false; } // 本输入模块是否应该激活 public virtual bool ShouldActivateModule() { return enabled && gameObject.activeInHierarchy; } // 反激活当前模块 public virtual void DeactivateModule() {} // 激活当前模块 public virtual void ActivateModule() {} // 更新当前模块 public virtual void UpdateModule() {} // 当前模块模块是否受支持 public virtual bool IsModuleSupported() { return true; }
// 向上一个[进入对象](也就是当前指针事件数据中存储的[进入对象])和其所有父节点发送离开事件 // 向当前的新的[进入对象]发送进入事件和其所有父节点发送进入事件 // 如果两个对象存在共同的父节点, n protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget) { // 如果没有新的[进入对象](指针游离在所有的对象之外), 或者当前的指针事件的[进入对象]被删除 // 那么就简单的向所有悬停对象(之前文章介绍过, 就是当前进入对象和其所有父节点链条上所有节点对象)发送离开事件, 下面会介绍 if (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 = newEnterTarget; return; } } // 如果[进入对象]存在且未变化, 则啥也不做 if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget) return; // ------------------------------------------------------- // 下面的区域处理的是一定有新的[进入对象], 但是不一定有上一个[进入对象] // 查找上一个[进入对象]和新的[进入对象]共同的父节点 GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget); // 如果有上一个[进入对象], 向"上一个[进入对象]一直向上直到共同父节点(或者没有)链条上所有的节点发送离开事件, 且不包含共同的父节点" if (currentPointerData.pointerEnter != null) { Transform t = currentPointerData.pointerEnter.transform; while (t != null) { // 遇到共同父节点停止 if (commonRoot != null && commonRoot.transform == t) break; ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler); currentPointerData.hovered.Remove(t.gameObject); t = t.parent; } } // 向新的[进入对象]一直到共同父节点(或者没有)链条上所有节点发送进入事件 currentPointerData.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; } } }
上面的代码说的有点迷糊, 我重构之后的代码可能会好理解一点:
// 向所有的悬停对象发送离开事件 private static void ExecutePointerExitForHover(PointerEventData currentPointerData, bool isClearPointerEnter) { if (currentPointerData.hovered.Count <= 0) return; foreach (var go in currentPointerData.hovered) { ExecuteEvents.Execute(go, currentPointerData, ExecuteEvents.pointerExitHandler); } currentPointerData.hovered.Clear(); if (isClearPointerEnter) currentPointerData.pointerEnter = null; } // 向target层级链条上所有对象发送离开事件 private static void ExecutePointerExitEventForChain(GameObject target, PointerEventData currentPointerData, GameObject stopGameObject = null) { var t = target.transform; while (t != null) { if (stopGameObject && stopGameObject == t.gameObject) break; ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler); currentPointerData.hovered.Remove(t.gameObject); t = t.parent; } } // 向target层级链条上所有对象发送进入事件 private static void ExecutePointerEnterEventForChain(GameObject target, PointerEventData currentPointerData, GameObject stopGameObject = null) { var t = target.transform; while (t != null) { if (stopGameObject && stopGameObject == t.gameObject) break; ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler); currentPointerData.hovered.Add(t.gameObject); t = t.parent; } } protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget) { // [进入对象]未变化, 直接返回 if (newEnterTarget && newEnterTarget == currentPointerData.pointerEnter) return; // 没有新的[进入对象], 向所有悬停对象发送离开事件(因为此时指针游离于所有对象之外), 直接返回(因为不用处理进入事件) if (newEnterTarget == null) { ExecutePointerExitForHover(currentPointerData, true); return; } var commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget); if (currentPointerData.pointerEnter != null) { // 有上一个[进入对象], 向其层级链条上所有对象发送离开事件 ExecutePointerExitEventForChain(currentPointerData.pointerEnter, currentPointerData, commonRoot); } else { // // 没有上一个[进入对象], 向所有悬停对象发送离开事件 ExecutePointerExitForHover(currentPointerData, false); } // 向新的[进入对象]层级链条上所有对象发送进入事件 currentPointerData.pointerEnter = newEnterTarget; ExecutePointerEnterEventForChain(newEnterTarget, currentPointerData, commonRoot); }
今天简单介绍了输入模块, 并对BaseInput和BaseInputModule做了详细的分析, 剩余的部分会在下一篇文章给出.
希望对大家有所帮助.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。