Unity中的UGUI源码解析之事件系统(8)-输入模块(中)

Unity中的UGUI源码解析之事件系统(8)-输入模块(中)

接上一篇文章, 继续介绍输入模块.

Unity中主要处理的是指针事件, 也就是在2d平面上跟踪指针设备输入坐标的的事件, 这一类事件有鼠标事件, 触摸事件等.

PointerInputModule

Unity将使用抽象类PointerInputModule定义指针输入的基本信息和基本函数, 由其子类StandaloneInputModule来具体实现.

相关类

TouchPhase

TouchPhase用来描述触摸阶段.

  • Began: 开始触摸
  • Moved: 移动
  • Stationary: 触摸, 静止
  • Ended: 抬起
  • Canceled: 取消(通过导航事件)

MouseButtonEventData

MouseButtonEventData用来封装鼠标按钮事件数据, 封装按钮事件数据和状态, 提供状态函数.

public class MouseButtonEventData
{
    /// 前面文章介绍过的按钮状态枚举, 表示每个按钮在当前帧的状态
    public PointerEventData.FramePressState buttonState;
    
    /// 指针事件数据
    public PointerEventData buttonData;

    /// 当前帧是否被按下
    public bool PressedThisFrame()
    {
        return buttonState == PointerEventData.FramePressState.Pressed || buttonState == PointerEventData.FramePressState.PressedAndReleased;
    }

    /// 当前帧是否被放开
    public bool ReleasedThisFrame()
    {
        return buttonState == PointerEventData.FramePressState.Released || buttonState == PointerEventData.FramePressState.PressedAndReleased;
    }
}

ButtonState

ButtonState用来封装按钮状态.

 protected class ButtonState
 {
     /// 前面文章介绍过的按钮枚举
     private PointerEventData.InputButton m_Button = PointerEventData.InputButton.Left;
     
     /// 鼠标事件数据封装体
     private MouseButtonEventData m_EventData;
     
     public MouseButtonEventData eventData
     {
         get { return m_EventData; }
         set { m_EventData = value; }
     }

     public PointerEventData.InputButton button
     {
         get { return m_Button; }
         set { m_Button = value; }
     }
 }

MouseState

MouseState用来封装鼠标状态. 主要跟踪所有的鼠标按钮, 提供设置和获取按钮状态的函数, 还有提供查询按下和放开的函数.

protected class MouseState
{
    private List<ButtonState> m_TrackedButtons = new List<ButtonState>();

    public bool AnyPressesThisFrame()
    {
        for (int i = 0; i < m_TrackedButtons.Count; i++)
        {
            if (m_TrackedButtons[i].eventData.PressedThisFrame())
                return true;
        }
        return false;
    }

    public bool AnyReleasesThisFrame()
    {
        for (int i = 0; i < m_TrackedButtons.Count; i++)
        {
            if (m_TrackedButtons[i].eventData.ReleasedThisFrame())
                return true;
        }
        return false;
    }

    /// 获取按钮状态, 如果没有则新建空白按钮状态
    public ButtonState GetButtonState(PointerEventData.InputButton button)
    {
        ButtonState tracked = null;
        for (int i = 0; i < m_TrackedButtons.Count; i++)
        {
            if (m_TrackedButtons[i].button == button)
            {
                tracked = m_TrackedButtons[i];
                break;
            }
        }

        if (tracked == null)
        {
            tracked = new ButtonState { button = button, eventData = new MouseButtonEventData() };
            m_TrackedButtons.Add(tracked);
        }
        return tracked;
    }

    public void SetButtonState(PointerEventData.InputButton button, PointerEventData.FramePressState stateForMouseButton, PointerEventData data)
    {
        var toModify = GetButtonState(button);
        toModify.eventData.buttonState = stateForMouseButton;
        toModify.eventData.buttonData = data;
    }
}

字段和属性

public abstract class PointerInputModule : BaseInputModule
{
    // --------------------
    // 鼠标按键标识, 都是负数, 触摸的标识都是正数
    /// 左键
    public const int kMouseLeftId = -1;
    
    /// 右键
    public const int kMouseRightId = -2;
    
    /// 中键
    public const int kMouseMiddleId = -3;
    // --------------------
    
    /// 缓存的指针事件数据
    protected Dictionary<int, PointerEventData> m_PointerData = new Dictionary<int, PointerEventData>();
    
    /// 鼠标状态
    private readonly MouseState m_MouseState = new MouseState();
}

公开函数

PointerInputModule的公开函数比较少, 一个是检测指针是给定id的指针是否位于某个对象区域, 一个是ToString.

public override bool IsPointerOverGameObject(int pointerId)
{
    // 通过获取指针的事件数据, 判断事件数据
    var lastPointer = GetLastPointerEventData(pointerId);
    if (lastPointer != null)
        return lastPointer.pointerEnter != null;
    return false;
}

public override string ToString()
{
    var sb = new StringBuilder("<b>Pointer Input Module of type: </b>" + GetType());
    sb.AppendLine();
    foreach (var pointer in m_PointerData)
    {
        if (pointer.Value == null)
            continue;
        sb.AppendLine("<B>Pointer:</b> " + pointer.Key);
        sb.AppendLine(pointer.Value.ToString());
    }
    return sb.ToString();
}

私有函数

// 判断是否开始拖拽, 可以使用EventSystem组件上的pixelDragThreshold
private static bool ShouldStartDrag(Vector2 pressPos, Vector2 currentPos, float threshold, bool useDragThreshold)
{
    if (!useDragThreshold)
        return true;

    return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
}

受保护函数

// 获取指定指针id的事件数据
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;
}

// 删除指定指针id的事件数据
protected void RemovePointerData(PointerEventData data)
{
    m_PointerData.Remove(data.pointerId);
}

// 获取指定指针id的事件数据, 不新建
protected PointerEventData GetLastPointerEventData(int id)
{
    PointerEventData data;
    GetPointerData(id, out data, false);
    return data;
}

// 获取触摸类型的事件数据
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;

    // 所有投射器投射此指针数据, 记录结果
    eventSystem.RaycastAll(pointerData, m_RaycastResultCache);

    // 查找第一个满足要求的被击中的对象
    var raycast = FindFirstRaycast(m_RaycastResultCache);
    
    // 记录当前集中对象
    pointerData.pointerCurrentRaycast = raycast;
    
    // 清空结果缓存
    m_RaycastResultCache.Clear();
    return pointerData;
}

// 拷贝事件数据, "@"是为了避免与关键字冲突(虽然这里的两个变量都不是关键字), 本身不是变量名的一部分
protected void CopyFromTo(PointerEventData @from, PointerEventData @to)
{
    @to.position = @from.position;
    @to.delta = @from.delta;
    @to.scrollDelta = @from.scrollDelta;
    @to.pointerCurrentRaycast = @from.pointerCurrentRaycast;
    @to.pointerEnter = @from.pointerEnter;
}

// 获取鼠标按钮当前帧的状态
protected PointerEventData.FramePressState StateForMouseButton(int buttonId)
{
    var pressed = input.GetMouseButtonDown(buttonId);
    var released = input.GetMouseButtonUp(buttonId);
    if (pressed && released)
        return PointerEventData.FramePressState.PressedAndReleased;
    if (pressed)
        return PointerEventData.FramePressState.Pressed;
    if (released)
        return PointerEventData.FramePressState.Released;
    return PointerEventData.FramePressState.NotChanged;
}

protected virtual MouseState GetMousePointerEventData()
{
    return GetMousePointerEventData(0);
}

// 获取鼠标指针事件数据, 这里的参数没有用, 设计者是想用来给子类重写的
protected virtual MouseState GetMousePointerEventData(int id)
{
    // 初始化指针数据, 默认左键
    PointerEventData leftData;
    var created = GetPointerData(kMouseLeftId, out leftData, true);

    leftData.Reset();

    // 如果新建的, 记录鼠标位置, 用于后面计算相对位移
    if (created)
        leftData.position = input.mousePosition;

    Vector2 pos = input.mousePosition;
    if (Cursor.lockState == CursorLockMode.Locked)
    { // 如果光标被锁定, 那么忽略位置信息
        // We don't want to do ANY cursor-based interaction when the mouse is locked
        leftData.position = new Vector2(-1.0f, -1.0f);
        leftData.delta = Vector2.zero;
    }
    else
    { // 计算相对位移和记录鼠标位置
        leftData.delta = pos - leftData.position;
        leftData.position = pos;
    }
    
    // 记录鼠标滚动量
    leftData.scrollDelta = input.mouseScrollDelta;
    
    // 标记为左键
    leftData.button = PointerEventData.InputButton.Left;
    
    // 获取投射结果
    eventSystem.RaycastAll(leftData, m_RaycastResultCache);
    
    // 查找第一个满足要求的被击中的对象
    var raycast = FindFirstRaycast(m_RaycastResultCache);
    
    // 记录当前集中对象
    leftData.pointerCurrentRaycast = raycast;
    
    // 清空结果缓存
    m_RaycastResultCache.Clear();

    // --------------------------------------------------------------
    // -- 复制左键数据到右键和中键
    PointerEventData rightData;
    GetPointerData(kMouseRightId, out rightData, true);
    CopyFromTo(leftData, rightData);
    rightData.button = PointerEventData.InputButton.Right;

    PointerEventData middleData;
    GetPointerData(kMouseMiddleId, out middleData, true);
    CopyFromTo(leftData, middleData);
    middleData.button = PointerEventData.InputButton.Middle;
    // --------------------------------------------------------------
    
    // 分别查询和设置左中右建的当前按钮状态
    m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
    m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
    m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);

    return m_MouseState;
}

// 处理移动事件, 在移动的过程中可能会触发进入和离开事件
protected virtual void ProcessMove(PointerEventData pointerEvent)
{
    // 如果光标锁定, 只处理离开事件
    var targetGO = (Cursor.lockState == CursorLockMode.Locked ? null : pointerEvent.pointerCurrentRaycast.gameObject);
    HandlePointerExitAndEnter(pointerEvent, targetGO);
}

// 处理拖拽事件
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;
    }

    // 处理拖拽过程中的事件
    if (pointerEvent.dragging)
    {
        // 在处理拖拽之前, 向上一个按下对象发送抬起事件(如果两个对象不一样), 并清空事件的点击状态
        // [这里是可以同时处理拖拽和点击的关键点]
        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);
    }
}

// 清空所有被选中的对象, 输入模块被反激活时使用
protected void ClearSelection()
{
    var baseEventData = GetBaseEventData();

    foreach (var pointer in m_PointerData.Values)
    {
    // 向所有悬停对象发送离开事件
        HandlePointerExitAndEnter(pointer, null);
    }

    // 清空指针事件数据
    m_PointerData.Clear();
    
    // 向当前焦点对象发送取消选择事件
    eventSystem.SetSelectedGameObject(null, baseEventData);
}

// 如果选择的对象发生变化, 则向当前焦点对象发送取消选择事件
protected void DeselectIfSelectionChanged(GameObject currentOverGo, BaseEventData pointerEvent)
{
    var selectHandlerGO = ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo);
    if (selectHandlerGO != eventSystem.currentSelectedGameObject)
        eventSystem.SetSelectedGameObject(null, pointerEvent);
}

总结

今天详细介绍了PointerInputModule, 内容不是很多, 相比大家都能看明白.

下篇文章介绍StandaloneInputModule, 应该是事件系统的最后一篇文章了, 只是在今天内容的基础上重写和添加了少量的内容, 也不是很复杂, 应该说Unity的C#层都不是很复杂, 复杂的是Cpp层.

好了, 今天就是这样了, 希望对大家有所帮助.

猜你喜欢

转载自blog.csdn.net/woodengm/article/details/124036986
今日推荐