NGUI的事件通知架构和源码剖析
2015-07-10 09:45
429 查看
NGUI的事件通知其实是由一个脚本UICamera来实现的,脚本的命名不是太好,其基本的原理很简单,在Update函数中检测用户输入,然后根据自己的策略分发到具体的物体。其定义了一些基本的通知回调函数,你可以查看具体的注释:
所以从字面上你就可以理解, 其提供了哪些事件通知,这些事件都是在在主线程中完成的。需要特别说明的是,NGUI有自己的事件通知,和MonoBehavior里面的函数OnMouseDown, OnMouseUp, OnMouseOver等消息处理函数重叠,所以只要我们使用了NGUI的处理框架以及NGUI的脚本,如UIButton,UISCrollView等,我们无需重载MonoBehavior的上述事件处理函数。 如果自己处理了,可能同一个用户输入会响应两次。
下面就简单介绍一下关键的函数或者结构体
通知函数, Notify
所以,消息通知是通过GameObject::SendMessage ()的方法来实现的,可以查看源码,等到funcName的名字都是上面的事件通知的名字,如: OnClick, OnHover, OnSelect等。
Raycast 帮助函数,如何从一个屏幕上的位置信息,找到点击,触摸,滑过的物体
ControlScheme 记录当前的输入的类型
MouseOrTouch 下面是记录鼠标事件和触摸事件的结构体,个人认为实现的不大合理,把鼠标和触摸需要的信息混合在一起了, NGUI在实现中也把touch,mouse事件合并进行了处理。
处理输入的事件
Update 函数,其处理UI 事件,并且发送给具体的物体和回调函数。
总结: 所有整个NGUI的事件处理通知都是由UICamera的update函数,分析当前帧的用户输入,然后发送时间给NGUI的UI控件,如UIButton,UIScrollView等。
/// * 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.
所以从字面上你就可以理解, 其提供了哪些事件通知,这些事件都是在在主线程中完成的。需要特别说明的是,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; }
所以,消息通知是通过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; }
ControlScheme 记录当前的输入的类型
public enum ControlScheme { Mouse, // 鼠标事件 Touch, // 触摸事件 Controller, // 控制器输入 }
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; } } }
处理输入的事件
// 处理鼠标按下或者触摸按下 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; } } } }
/// <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(); .................. }
总结: 所有整个NGUI的事件处理通知都是由UICamera的update函数,分析当前帧的用户输入,然后发送时间给NGUI的UI控件,如UIButton,UIScrollView等。
相关文章推荐
- 架构纵横谈之二 ---- 架构的模式与要点
- BS项目中的CSS架构_仅加载自己需要的CSS
- 关于三种主流WEB架构的思考
- Android操作系统的架构设计分析
- w3c技术架构介绍
- linux学习笔记 linux目录架构
- mysql数据库应付大流量网站的的3种架构扩展方式介绍
- 从零开始搭建MySQL MMM架构
- C/S和B/S两种架构的概念、区别和联系
- SequoiaDB 笔记
- Web服务器Nginx多方位优化策略
- 面试:(设计,架构)
- 十日谈
- 微信技术总监周颢:一亿用户背后架构秘密
- 大型网站架构改进历程:存储的瓶颈(3)
- Mysql在大型网站的应用架构演变
- 大型网站架构改进历程:存储的瓶颈(1)
- 大型网站架构改进历程:存储的瓶颈(2)
- 大型网站架构改进历程:存储的瓶颈(4)
- 大型网站架构改进历程:存储的瓶颈(5)