UGUI源码试探究 (二) 事件系统

UGUI源码试探究 (二) 事件系统

1. 按钮响应的过程

按钮注册事件
这里写图片描述

设置断点, 鼠标点击按钮, 触发断点

这里写图片描述

查看此时的调用堆栈

这里写图片描述

我们来到栈底: 鼠标按下之后, EventSystem.Update调用了第一个方法

这里写图片描述

来到了StandaloneInputModule.Process

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

如果鼠标抬起的元素跟鼠标按下的元素一样的话

这里写图片描述

如何获得鼠标按下的元素呢?
由下图可知, MouseButtonEventData记录了鼠标进入的物体(Text)和鼠标按下的物体(Button)

这里写图片描述

如何获得鼠标抬起的元素呢?

通过射线检测 , pointerEvent.pointerCurrentRaycast.gameObject可以获得当前鼠标抬起的物体为Text (UnityEngine.GameObject)

这里写图片描述

GetEventHandler可以找到Text (UnityEngine.GameObject)其父物体上实现了IPointerClickHandler接口的Button (UnityEngine.GameObject)

这里写图片描述

相关方法有:

    /// <summary>
    /// Bubble the specified event on the game object, figuring out which object will actually receive the event.
    /// 从root物体开始(向其父物体遍历), 找出哪个对象实际上会处理指定事件。
    /// </summary>
    public static GameObject GetEventHandler<T>(GameObject root) where T : IEventSystemHandler
    {
        if (root == null)
            return null;

        Transform t = root.transform;
        while (t != null)
        {
            if (CanHandleEvent<T>(t.gameObject))
                return t.gameObject;
            t = t.parent;
        }
        return null;
    }

    /// <summary>
    /// Whether the specified game object will be able to handle the specified event.
    /// 指定的对象是否能够处理指定的事件。
    /// </summary>
    public static bool CanHandleEvent<T>(GameObject go) where T : IEventSystemHandler{
        var internalHandlers = s_HandlerListPool.Get();
        GetEventList<T>(go, internalHandlers);
        var handlerCount = internalHandlers.Count;
        s_HandlerListPool.Release(internalHandlers);
        return handlerCount != 0;
    }

ExecuteEvents.Execute执行的是实现了IPointerClickHandler接口OnPointerClick方法的物体(就是Button啦)

这里写图片描述

我们来到了ButtonOnPointerClick方法, 发现它调用了Press方法!!!

这里写图片描述

然后如果我们在某个自定义方法例如SetImageColor注册了按钮的onClick方法, 就会执行该方法的代码了

这里写图片描述

    public ButtonClickedEvent onClick
    {
        get { return m_OnClick; }
        set { m_OnClick = value; }
    }

2. 事件触发的过程

如果不是按钮, 想要响应鼠标点击事件呢??

继承IPointerClickHandler方法就好了, 可以使用EventTrigger组件

这里写图片描述

例如在Image上面添加ventTrigger组件, 添加PointerClick事件

这里写图片描述

3. 射线检测的过程

鼠标如何检测到物体的呢???

由这里我们可以看出leftButtonData已经储存了鼠标按下的信息,

又因为GetMousePointerEventData(id) ->mouseData -> leftButtonData

这里写图片描述

来到GetMousePointerEventData, 发现它的功能是填充鼠标左, 中, 右三键信息

m_RaycastResultCache.Clear处加入断点, 可以查看相关信息

这里写图片描述

进入RaycastAll中查看, 此时的moduleCanvas

这里写图片描述

按下F11, 来到了GraphicRaycaster.Raycast, Raycast有两个重载函数


//①
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
//②
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)

Raycast函数①是用于筛选三个条件的

这里写图片描述

函数①的执行步骤为:

  1. 通过BlockingObjectsBlockingMask获取hitDistance

    • BlockingObjects: 射线检测时, 可以阻塞射线的物体(None / TwoD / Three D / All)
    • BlockingMask: 射线检测时, 对哪一些layer检测
    • hitDistance: 射线碰撞到第一个指定类型物体时两点的射线的距离(RaycastHit2D.distance)
  2. 通过Raycast函数②筛选符合条件的Graphic物体

    这里写图片描述

    函数②筛选条件为:

    • RectangleContainsScreenPoint判断pointerPosition是否在graphic.rectTransform范围内(即判断x, y值, 此时Image不在范围内, 被剔除)

    • WorldToScreenPoint判断pointerPosition.z是否在相机可见的范围内(即判断z值)

    • graphic.Raycast通过ICanvasRaycastFilter.IsRaycastLocationValid判断屏幕上的点pointerPosition是否满足检测有效条件

      • Image, Mask, RectMask2D中继承接口ICanvasRaycastFilter并重写方法IsRaycastLocationValid
        这里写图片描述

      • Graphic中继承接口ICanvasRaycastFilter并调用方法IsRaycastLocationValid
        这里写图片描述
        这里写图片描述

  3. 通过ignoreReversedGraphics筛选最后符合条件的可以响应鼠标检测的物体

    • 通过两个向量的点乘结果判断
if (ignoreReversedGraphics)
{
    if (currentEventCamera == null)
    {
        // If we dont have a camera we know that we should always be facing forward
        // 如果没有相机,屏幕的正前方与物体的向量点乘
        var dir = go.transform.rotation * Vector3.forward;
        appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
    }
    else
    {
        // If we have a camera compare the direction against the cameras forward.
        // 如果有相机,相机的正前方与物体的向量点乘
        var cameraFoward = currentEventCamera.transform.rotation * Vector3.forward;
        var dir = go.transform.rotation * Vector3.forward;
        appendGraphic = Vector3.Dot(cameraFoward, dir) > 0;
    }
}
  • distance(射线到物体所在平面的距离)与hitDistance(射线碰撞到第一个BlockingObjects时射线的距离)做比较
if (appendGraphic)
{
    float distance = 0;

    if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
        distance = 0;
    else
    {
        Transform trans = go.transform;
        Vector3 transForward = trans.forward;
        // 计算射线到物体所在平面的距离
        // 算法参考 http://geomalgorithms.com/a06-_intersect-2.html
        distance = (Vector3.Dot(transForward, trans.position - currentEventCamera.transform.position) / Vector3.Dot(transForward, ray.direction));

        // Check to see if the go is behind the camera.
        if (distance < 0)
            continue;
    }

    if (distance >= hitDistance)
        continue;

    var castResult = new RaycastResult
    {
        gameObject = go,
        module = this,
        distance = distance,
        screenPosition = eventPosition,
        index = resultAppendList.Count,
        depth = m_RaycastResults[index].depth,
        sortingLayer = canvas.sortingLayerID,
        sortingOrder = canvas.sortingOrder
    };
    resultAppendList.Add(castResult);
}

按下F11, RaycastAll出栈, 通过FindFirstRaycast获得第一个射线检测到的物体为Text, 然后就可以进行愉快地操作(按钮响应, 事件触发)啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值