NGUI的事件通知架构和源码剖析

来源:互联网 发布:python发送http请求 编辑:程序博客网 时间:2024/06/03 19:09

NGUI的事件通知其实是由一个脚本UICamera来实现的,脚本的命名不是太好,其基本的原理很简单,在Update函数中检测用户输入,然后根据自己的策略分发到具体的物体。其定义了一些基本的通知回调函数,你可以查看具体的注释:

/// * OnHover (isOver) is sent when the mouse hovers over a collider or moves away./// * OnPress (isDown) is sent when a mouse button gets pressed on the collider./// * OnSelect (selected) is sent when a mouse button is first pressed on an object. Repeated presses won't result in an OnSelect(true)./// * OnClick () is sent when a mouse is pressed and released on the same object.///   UICamera.currentTouchID tells you which button was clicked./// * OnDoubleClick () is sent when the click happens twice within a fourth of a second.///   UICamera.currentTouchID tells you which button was clicked./// /// * OnDragStart () is sent to a game object under the touch just before the OnDrag() notifications begin./// * OnDrag (delta) is sent to an object that's being dragged./// * OnDragOver (draggedObject) is sent to a game object when another object is dragged over its area./// * OnDragOut (draggedObject) is sent to a game object when another object is dragged out of its area./// * OnDragEnd () is sent to a dragged object when the drag event finishes./// /// * OnTooltip (show) is sent when the mouse hovers over a collider for some time without moving./// * OnScroll (float delta) is sent out when the mouse scroll wheel is moved./// * OnKey (KeyCode key) is sent when keyboard or controller input is used.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

所以从字面上你就可以理解, 其提供了哪些事件通知,这些事件都是在在主线程中完成的。需要特别说明的是,NGUI有自己的事件通知,和MonoBehavior里面的函数OnMouseDown, OnMouseUp, OnMouseOver等消息处理函数重叠,所以只要我们使用了NGUI的处理框架以及NGUI的脚本,如UIButton,UISCrollView等,我们无需重载MonoBehavior的上述事件处理函数。 如果自己处理了,可能同一个用户输入会响应两次。

下面就简单介绍一下关键的函数或者结构体

  • 通知函数, Notify
    /// <summary>    /// Generic notification function. Used in place of SendMessage to shorten the code and allow for more than one receiver.    /// </summary>    static public void Notify (GameObject go, string funcName, object obj)    {        if (mNotifying) return;        mNotifying = true;        if (NGUITools.GetActive(go))        {           // 基本的Unity的GameObject函数.            go.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);            if (mGenericHandler != null && mGenericHandler != go)            {                mGenericHandler.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);            }        }        mNotifying = false;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

所以,消息通知是通过GameObject::SendMessage ()的方法来实现的,可以查看源码,等到funcName的名字都是上面的事件通知的名字,如: OnClick, OnHover, OnSelect等。

  • Raycast 帮助函数,如何从一个屏幕上的位置信息,找到点击,触摸,滑过的物体
    /// <summary>    /// Returns the object under the specified position.    /// </summary>    static public bool Raycast (Vector3 inPos)    {        for (int i = 0; i < list.size; ++i)        {            // 当前的UI Camera            UICamera cam = list.buffer[i];            // Skip inactive scripts             if (!cam.enabled || !NGUITools.GetActive(cam.gameObject)) continue;            // Convert to view space            currentCamera = cam.cachedCamera;             // 将屏幕的位置信息,转换成ViewPort的信息,viewport的空间在0, 1范围内            Vector3 pos = currentCamera.ScreenToViewportPoint(inPos);            if (float.IsNaN(pos.x) || float.IsNaN(pos.y)) continue;            // If it's outside the camera's viewport, do nothing            if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f) continue;            // Cast a ray into the screen            Ray ray = currentCamera.ScreenPointToRay(inPos);            // Raycast into the screen            int mask = currentCamera.cullingMask & (int)cam.eventReceiverMask;            float dist = (cam.rangeDistance > 0f) ? cam.rangeDistance : currentCamera.farClipPlane - currentCamera.nearClipPlane;            if (cam.eventType == EventType.World_3D)            {              ............            }            else if (cam.eventType == EventType.UI_3D)            {                // 获取当前的所有的RaycastHit的列表,所以在此,需要特别说明的是,任何的NGUI的控件都需要加上BoxColider物体。 disk和camera的距离,mask和层                RaycastHit[] hits = Physics.RaycastAll(ray, dist, mask);                if (hits.Length > 1)                {                    // 下面所有的代码获取了点击到的物体的列表, 放入到全局的静态变量mhits中                    // 基本的结构单元为:                    //  struct DepthEntry                    //{                    //  public int depth; // Gameobject 的深度信息                    //  public RaycastHit hit; // RaycastHit信息,可以查看具体的unity文档                    //  public Vector3 point; // hit物体的世界空间的地址                    //  public GameObject go; // hit 物体                    //}                    for (int b = 0; b < hits.Length; ++b)                    {                        GameObject go = hits[b].collider.gameObject;                        UIWidget w = go.GetComponent<UIWidget>();                        if (w != null)                        {                            if (!w.isVisible) continue;                            if (w.hitCheck != null && !w.hitCheck(hits[b].point)) continue;                        }                        else                        {                            UIRect rect = NGUITools.FindInParents<UIRect>(go);                            if (rect != null && rect.finalAlpha < 0.001f) continue;                        }                        mHit.depth = NGUITools.CalculateRaycastDepth(go);                        if (mHit.depth != int.MaxValue)                        {                            mHit.hit = hits[b];                            mHit.point = hits[b].point;                            mHit.go = hits[b].collider.gameObject;                            mHits.Add(mHit);                        }                    }                    // 按照深度升序排序,                    mHits.Sort(delegate(DepthEntry r1, DepthEntry r2) { return r2.depth.CompareTo(r1.depth); });                     // 找到depth最高的可视的物体为当前的接触的物体                    for (int b = 0; b < mHits.size; ++b)                    {#if UNITY_FLASH                        if (IsVisible(mHits.buffer[b]))#else                        if (IsVisible(ref mHits.buffer[b]))#endif                        {                            lastHit = mHits[b].hit;                            hoveredObject = mHits[b].go;                            lastWorldPosition = mHits[b].point;                            mHits.Clear();                            return true;                        }                    }                    mHits.Clear();                }                else if (hits.Length == 1)                {                ............                }            }            else if (cam.eventType == EventType.World_2D)            {             ..............            }            else if (cam.eventType == EventType.UI_2D)            {             ..............            }        }        return false;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • ControlScheme 记录当前的输入的类型
    public enum ControlScheme    {        Mouse,  // 鼠标事件        Touch,  // 触摸事件        Controller, // 控制器输入    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • MouseOrTouch 下面是记录鼠标事件和触摸事件的结构体,个人认为实现的不大合理,把鼠标和触摸需要的信息混合在一起了, NGUI在实现中也把touch,mouse事件合并进行了处理。
    /// <summary>    /// Ambiguous mouse, touch, or controller event.    /// </summary>    public class MouseOrTouch    {        public Vector2 pos;             // Current position of the mouse or touch event        public Vector2 lastPos;         // Previous position of the mouse or touch event        public Vector2 delta;           // Delta since last update        public Vector2 totalDelta;      // Delta since the event started being tracked        public Camera pressedCam;       // Camera that the OnPress(true) was fired with        public GameObject last;         // Last object under the touch or mouse        public GameObject current;      // Current game object under the touch or mouse        public GameObject pressed;      // Last game object to receive OnPress        public GameObject dragged;      // Game object that's being dragged        public float pressTime = 0f;    // When the touch event started        public float clickTime = 0f;    // The last time a click event was sent out        public ClickNotification clickNotification = ClickNotification.Always;        public bool touchBegan = true;        public bool pressStarted = false;        public bool dragStarted = false;        /// <summary>        /// Delta time since the touch operation started.        /// </summary>        public float deltaTime { get { return touchBegan ? RealTime.time - pressTime : 0f; } }        /// <summary>        /// Returns whether this touch is currently over a UI element.        /// </summary>        public bool isOverUI        {            get            {                return current != null && current != fallThrough && NGUITools.FindInParents<UIRoot>(current) != null;            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 处理输入的事件
    // 处理鼠标按下或者触摸按下    void ProcessPress (bool pressed, float click, float drag)    {        // Send out the press message        if (pressed)        {            if (mTooltip != null) ShowTooltip(false);            currentTouch.pressStarted = true;            // 全局的OnPress的事件通知,前面的物体被释放            if (onPress != null && currentTouch.pressed)                onPress(currentTouch.pressed, false);            // OnPress 的事件通知, false,前面的物体            Notify(currentTouch.pressed, "OnPress", false);            currentTouch.pressed = currentTouch.current;            currentTouch.dragged = currentTouch.current;            currentTouch.clickNotification = ClickNotification.BasedOnDelta;            currentTouch.totalDelta = Vector2.zero;            currentTouch.dragStarted = false;           // 全局的OnPress的事件通知,当前的物体被按下            if (onPress != null && currentTouch.pressed)                onPress(currentTouch.pressed, true);           // OnPress 的事件通知, true,当前的物体            Notify(currentTouch.pressed, "OnPress", true);            // Update the selection            if (currentTouch.pressed != mCurrentSelection)            {                if (mTooltip != null) ShowTooltip(false);                currentScheme = ControlScheme.Touch;                selectedObject = currentTouch.pressed;            }        }        // 此处就是处理Drag的各项事务, 注意条件        else if (currentTouch.pressed != null && (currentTouch.delta.sqrMagnitude != 0f || currentTouch.current != currentTouch.last))        {            // Keep track of the total movement            currentTouch.totalDelta += currentTouch.delta;            float mag = currentTouch.totalDelta.sqrMagnitude;            bool justStarted = false;            // If the drag process hasn't started yet but we've already moved off the object, start it immediately            if (!currentTouch.dragStarted && currentTouch.last != currentTouch.current)            {                currentTouch.dragStarted = true;                currentTouch.delta = currentTouch.totalDelta;                // OnDragOver is sent for consistency, so that OnDragOut is always preceded by OnDragOver                isDragging = true;                // 通知OnDragStart 事件到全局函数和现在的物体                if (onDragStart != null) onDragStart(currentTouch.dragged);                Notify(currentTouch.dragged, "OnDragStart", null);                // 通知OnDragOver 事件到全局函数和上次Drag的物体                if (onDragOver != null) onDragOver(currentTouch.last, currentTouch.dragged);                Notify(currentTouch.last, "OnDragOver", currentTouch.dragged);                isDragging = false;            }            else if (!currentTouch.dragStarted && drag < mag)            {                // If the drag event has not yet started, see if we've dragged the touch far enough to start it                justStarted = true;                currentTouch.dragStarted = true;                currentTouch.delta = currentTouch.totalDelta;            }            // If we're dragging the touch, send out drag events            // 判断DragStarted 发送相关的事件,如            if (currentTouch.dragStarted)            {                if (mTooltip != null) ShowTooltip(false);                isDragging = true;                bool isDisabled = (currentTouch.clickNotification == ClickNotification.None);                if (justStarted)                {                    if (onDragStart != null) onDragStart(currentTouch.dragged);                    Notify(currentTouch.dragged, "OnDragStart", null);                    if (onDragOver != null) onDragOver(currentTouch.last, currentTouch.dragged);                    Notify(currentTouch.current, "OnDragOver", currentTouch.dragged);                }                else if (currentTouch.last != currentTouch.current)                {                    if (onDragStart != null) onDragStart(currentTouch.dragged);                    Notify(currentTouch.last, "OnDragOut", currentTouch.dragged);                    if (onDragOver != null) onDragOver(currentTouch.last, currentTouch.dragged);                    Notify(currentTouch.current, "OnDragOver", currentTouch.dragged);                }                if (onDrag != null) onDrag(currentTouch.dragged, currentTouch.delta);                Notify(currentTouch.dragged, "OnDrag", currentTouch.delta);                currentTouch.last = currentTouch.current;                isDragging = false;                if (isDisabled)                {                    // If the notification status has already been disabled, keep it as such                    currentTouch.clickNotification = ClickNotification.None;                }                else if (currentTouch.clickNotification == ClickNotification.BasedOnDelta && click < mag)                {                    // We've dragged far enough to cancel the click                    currentTouch.clickNotification = ClickNotification.None;                }            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
/// <summary>/// 处理Touch,和Mouse release事件。/// </summary>void ProcessRelease (bool isMouse, float drag){    // Send out the unpress message    currentTouch.pressStarted = false;    if (mTooltip != null) ShowTooltip(false);    if (currentTouch.pressed != null)    {        // If there was a drag event in progress, make sure OnDragOut gets sent        if (currentTouch.dragStarted)        {            if (onDragOut != null) onDragOut(currentTouch.last, currentTouch.dragged);            Notify(currentTouch.last, "OnDragOut", currentTouch.dragged);            if (onDragEnd != null) onDragEnd(currentTouch.dragged);            Notify(currentTouch.dragged, "OnDragEnd", null);        }        // Send the notification of a touch ending        if (onPress != null) onPress(currentTouch.pressed, false);        Notify(currentTouch.pressed, "OnPress", false);        // Send a hover message to the object        if (isMouse)        {            if (onHover != null) onHover(currentTouch.current, true);            Notify(currentTouch.current, "OnHover", true);        }        mHover = currentTouch.current;        // If the button/touch was released on the same object, consider it a click and select it        if (currentTouch.dragged == currentTouch.current ||            (currentScheme != ControlScheme.Controller &&            currentTouch.clickNotification != ClickNotification.None &&            currentTouch.totalDelta.sqrMagnitude < drag))        {            if (currentTouch.pressed != mCurrentSelection)            {                mNextSelection = null;                mCurrentSelection = currentTouch.pressed;                if (onSelect != null) onSelect(currentTouch.pressed, true);                Notify(currentTouch.pressed, "OnSelect", true);            }            else            {                mNextSelection = null;                mCurrentSelection = currentTouch.pressed;            }            // If the touch should consider clicks, send out an OnClick notification            if (currentTouch.clickNotification != ClickNotification.None && currentTouch.pressed == currentTouch.current)            {                float time = RealTime.time;                if (onClick != null) onClick(currentTouch.pressed);                Notify(currentTouch.pressed, "OnClick", null);                if (currentTouch.clickTime + 0.35f > time)                {                    if (onDoubleClick != null) onDoubleClick(currentTouch.pressed);                    Notify(currentTouch.pressed, "OnDoubleClick", null);                }                currentTouch.clickTime = time;            }        }        else if (currentTouch.dragStarted) // The button/touch was released on a different object        {            // Send a drop notification (for drag & drop)            if (onDrop != null) onDrop(currentTouch.current, currentTouch.dragged);            Notify(currentTouch.current, "OnDrop", currentTouch.dragged);        }    }    currentTouch.dragStarted = false;    currentTouch.pressed = null;    currentTouch.dragged = null;}
  • Update 函数,其处理UI 事件,并且发送给具体的物体和回调函数。
    void Update ()    {        // Only the first UI layer should be processing events#if UNITY_EDITOR        if (!Application.isPlaying || !handlesEvents) return;#else        if (!handlesEvents) return;#endif        current = this;        // Process touch events first          // 处理触摸和鼠标事件,可以review源代码,都是复用了ProcessPress和ProcessRelease        // 函数        if (useTouch) ProcessTouches ();        else if (useMouse) ProcessMouse();        ..................    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

总结: 所有整个NGUI的事件处理通知都是由UICamera的update函数,分析当前帧的用户输入,然后发送时间给NGUI的UI控件,如UIButton,UIScrollView等。

0 0