NGUI源码学习——UICamera

转载自
UICamera
NGUI源码学习_UICamera

0 前言

UICamera是NGUI中专门用于捕获和分发交互事件的脚本,和UI渲染无关,需要挂在UI摄像机上。
其核心思想是在Update中检测Input的各种输入情况,并对屏幕做raycast投射,以决定是哪个go的collider触发事件,最终将事件分发出去。

Q: 检测各种输入在哪里做?

  • Update()中.

Q: UICamera如何作事件分发

在这里插入图片描述

1 核心数据结构

有许多公共静态属性. 如 currentXXX .
用于存放触发事件的摄像机 / ray 等等.

EventTpye

在这里插入图片描述
分为:

  • World_3D
  • UI_3D
  • World_2D
  • UI_2D

其中3D用Physics.Raycast实现,2D用Physics.OverlapPoint实现

  • World会根据触发点的 世界距离 排序(常用于游戏摄像机)
  • UI会根据 widget depth 排序(常用于UI界面)

ControlScheme

在这里插入图片描述
有三种类型: 鼠标 / 触摸 / 手柄
触犯事件会根据这个类型做出相应的调整. 不同设备不同的处理方式?

MouseOrTouch

MouseOrTouch是一个UI事件的抽象,用于保存输入数据中的位置、偏移量等重要信息。

public class MouseOrTouch
{
    public Vector2 pos;               // 当前鼠标或触摸的位置
    public Vector2 lastPos;           // 上一次鼠标或触摸的位置
    public Vector2 delta;             // 当前帧与上一帧的偏移
    public Vector2 totalDelta;        // delta的累积,通常用于drag事件

    public Camera pressedCam;         // OnPress(true)触发时对应的捕获事件的摄像机

    public GameObject last;           // 上一个触发触摸或鼠标事件的go
    public GameObject current;        // 当前触发触摸或鼠标事件的go
    public GameObject pressed;        // 上一个接收OnPress的go
    public GameObject dragged;        // 正在被拖拽的go

    public float clickTime = 0f;      // 上一次click事件的时间(通常用于判断doubleClick)

    public ClickNotification clickNotification = ClickNotification.Always;  // OnClick的触发条件,None为不触发,Always为总是触发,BasedOnDelta为根据位置移动的偏移量来决定是否发生(偏移量和Thresholds参数有关)
    public bool touchBegan = true;    // Touch模式下标识一个触摸是否为开始
    public bool pressStarted = false; // 标识是否开始按住
    public bool dragStarted = false;  // 标识是否开始拖拽
}

有了MouseOrTouch,UICamera就通过它来保存不同输入设备对应的输入数据。

在这里插入图片描述

mMouse、mTouches、controller就分别对应了鼠标、触摸屏和手柄等的输入数据。

  • 鼠标分左键、右键和滚轮,包含3个MouseOrTouch对象的数组。
  • 触摸屏会有多点触控发生。是一个列表。

2 核心方法

2.1 Notify(GameObject go, string funcName, object obj)

这个方法本质上调用的是 go.SendMessage(funcName, obj).
这样就能触发到 具体的UI控件中与funcName同名的方法
正因为是通过这种特殊方式来调用,所以控件源码中一部分方法会查找不到引用,和反射的道理类似.

同时也会发一份消息到genericEventHandler这个用户自己设置的全局go
相当于一个全局的事件接收器。

    static public void Notify(GameObject go, string funcName, object obj)
    {
        if (mNotifying > 10) return;

        // Automatically forward events to the currently open popup list
        if (currentScheme == ControlScheme.Controller && UIPopupList.isOpen &&
            UIPopupList.current.source == go && UIPopupList.isOpen)
            go = UIPopupList.current.gameObject;
		
		// 被触发的不为空,且在场景中?
        if (go && go.activeInHierarchy)
        {
            ++mNotifying;
            //if (currentScheme == ControlScheme.Controller)
            //	Debug.Log((go != null ? "[" + go.name + "]." : "[global].") + funcName + "(" + obj + ");", go);
            go.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);
            if (mGenericHandler != null && mGenericHandler != go)
                mGenericHandler.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);
            --mNotifying;
        }   
    }

2.2 Update

本质上是对UnityEngine.Input的封装和处理。

  1. 根据useTouch或useMouse标记(在Awake中根据当前平台设置,如手机只有touch),选择执行ProcessTouches或ProcessMouse
  2. 调用用户自定义的委托onCustomInput
  3. ProcessOthers处理键盘和手柄
  4. 处理tooltip相关逻辑

// Update核心函数

    void ProcessEvents()
    {
        current = this;
        NGUIDebug.debugRaycast = debug;

        // Process touch events first
        if (useTouch) ProcessTouches();
        else if (useMouse) ProcessMouse();

        // Custom input processing
        if (onCustomInput != null) onCustomInput();

        // Update the keyboard and joystick events
        if ((useKeyboard || useController) && !disableController && !ignoreControllerInput) ProcessOthers();

        // If it's time to show a tooltip, inform the object we're hovering over
        if (useMouse && mHover != null)
        {
            float scroll = !string.IsNullOrEmpty(scrollAxisName) ? GetAxis(scrollAxisName) : 0f;

            if (scroll != 0f)
            {
                if (onScroll != null) onScroll(mHover, scroll);
                Notify(mHover, "OnScroll", scroll);
            }

            if (currentScheme == ControlScheme.Mouse && showTooltips && mTooltipTime != 0f && !UIPopupList.isOpen && mMouse[0].dragged == null &&
                (mTooltipTime < RealTime.time || GetKey(KeyCode.LeftShift) || GetKey(KeyCode.RightShift)))
            {
                currentTouch = mMouse[0];
                currentTouchID = -1;
                ShowTooltip(mHover);
            }
        }

        if (mTooltip != null && !NGUITools.GetActive(mTooltip))
            ShowTooltip(null);

        current = null;
        currentTouchID = -100;
    }

ProcessTouches 为例,该方法中用 Input.GetTouch 获取每个touch分别做如下处理:

  • 创建MouseOrTouch结构并设置数据 currentTouch ;
  • 调用 ProcessTouch 分发事件 ;
  • 若touch数目为0,则转为ProcessMouse或ProcessFakeTouches(用于编辑器用鼠标模拟触摸);

接下来. 我们详细来看一下 ProcessTouch

  • 根据传入的pressed.处理的函数为: ProcessPress.
    • 向currentTouch.pressed分发OnPress事件;
void ProcessPress(bool pressed, float click, float drag);
  • 根据传入的released. 处理的函数为: ProcessRelease
    • 向currentTouch.last分发OnDragOut事件;
    • 向currentTouch.dragged分发OnDragEnd事件;
    • 向currentTouch.pressed分发OnPress事件;

2.3 RayCast(Vector3 inPos)

给定位置判断有没有与控件产生交互
最终要得到mRayHitObject这个结果。

  • currentCamera.ScreenToViewportPoint(inPos) : 算出viewport的坐标,并排除一些异常情况
    此方法的功能是实现坐标点position 从屏幕坐标系向摄像机视口的单位化坐标系转换
    参考点position的x和y分量为屏幕的实际坐标值,单位为像素,z值无效。

  • 【注:屏幕坐标左下角是(0,0),右上角是(pixelWidth,pixelHeight)】,viewport坐标右上角是(1,1)】

  • currentCamera.ScreenPointToRay(inPos) 将屏幕坐标转换为ray。

  • UICamera有个表示射线长度的参数rangeDistance,默认为摄像机远近裁剪面的距离;

  • eventReceiverMask表示摄像机投射ray时哪些层可以响应

  • 接下来根据EventType采用不同的算法来算ray射到的物体,以两种3D模式为例:

  • World_3D :

if (Physics.Raycast(ray, out lastHit, dist, mask)) hoveredObject = lastHit.collider.gameObject
  • UI_3D :
Physics.RaycastAll(ray, dist, mask)

获取射线穿到的所有hit,取每个hit对应的collider的go,计算其raycastDepth,并按从大到小排序
mRayHitObject= 上述最大的,且对应panel可见的hit对应的go

[定义:UIWidget.raycastDepth = 自身depth + 所属panel.depth * 1000]
[定义:NGUITools.CalculateRaycastDepth(go)计算go下所有可用widget的raycastDepth的最小值]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值