NGUI所見即所得之UICamera

NGUI所見即所得之UICamera

 

       UI,除了界面的顯示,還有一個重要的元素:事件響應。MoneBehaviour提供一些事件提供了一些函數接口(OnMouseUp,OnMouseDown等),只要MonBehaviour的子類實現這相應的方法以及方法執行的條件達到,Unity底層就會分發調用執行這個函數。一般地,UI事件響應處理機制會有4個基本元素:

 

      1.event object:事件對象,即當前事件的類型,如鼠標左鍵按下等。

      2.event source:事件源,或事件的觸發器,比如說,鼠標左鍵單擊點擊一個button,那麼button就是event source,鼠標左鍵單擊就是event source。

      3.event handle:事件處理方法。

      4.event listener:事件的監聽,比如上面說的鼠標左鍵點擊一個button,event listener就是監聽打button的一個mouse click事件,然後分發調用對應的event handle進行處理

 

        一般event source不會單獨存在,經常會跟event handle綁定在一起,其實就是指定了不同的event handle就是不同event source,如Button就有單擊雙擊事件,Input就有輸入焦點事件,event listener就好像人的大腦,監控這個所有的事件,同時作出不同的響應。

     NGUI自己組織了一套UI事件響應處理機制, 不是對MonoBehaviour的方法的封裝調用,UICamera就是NGUI框架中的event listener,原理很簡單:在Update中捕獲鼠標,鍵盤等設備的輸入(也可以狹義的認爲UICamera就是event listener),判斷不同event object 和event source,然後“廣播”分發執行event handle,下面附上分發的函數:

C#代碼  收藏代碼
  1. /// <summary>  
  2. /// Generic notification function. Used in place of SendMessage to shorten the code and allow for more than one receiver.  
  3. /// </summary>  
  4.   
  5. static public void Notify (GameObject go, string funcName, object obj)  
  6. {  
  7.     if (go != null)  
  8.     {  
  9.         go.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);  
  10.   
  11.         if (genericEventHandler != null && genericEventHandler != go)  
  12.         {  
  13.             genericEventHandler.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);  
  14.         }  
  15.     }  
  16. }  

       知道的誰是event listener,那要怎麼實現event handle。實現NGUI的事件的方法有很多種,③給了三種方式監聽NGUI的事件方法。文末貼了NGUI支持的event handle。

                                                                                                                                                                                                                   增補於:11/10/2013 8:45,感覺之前的沒頭沒尾的,就加了上面的鋪墊

 

       記得剛開始用NGUI的時候,就有心思要去琢磨下UICamera,那個時候NGUI還是2.6的版本,現在已經到了3.0.3f,NGUI更新真的很強勁, 當然改動也挺大的,特性也越來越多了。之前本來研究下UICamera最後還是放棄了,因爲UICamera的代碼太複雜了,很凌亂,也就放下去了,這幾天重新翻看了下,發現UICamera的可讀性太強了,代碼的組織邏輯很強,完全可以當做文本來從上到下來閱讀,所以纔會有這篇文章。

       UICamera做了很多有優化,新增了一些特性,之前可能覺得NGUI只是做一個工具,現在越來越完美了,少廢話,下面把看到的亮點呈上。

ClickNotification

C#代碼  收藏代碼
  1.        /// <summary>  
  2. /// Whether the touch event will be sending out the OnClick notification at the end.  
  3. /// </summary>  
  4.   
  5. public enum ClickNotification  
  6. {  
  7.     None,  
  8.     Always,  
  9.     BasedOnDelta,  
  10. }  

       ClickNotification定義了OnClick響應的條件,後面也定義了ClickNotification變量 public ClickNotification clickNotification = ClickNotification.Always;

       ClickNotification.None: 不響應OnClick事件

       ClickNotification.Always:總是響應OnClick事件

       ClickNotification.BaseOnDelta:依據移動的delta的距離判斷是否響應OnClick函數,如果移動距離大於float click  = isMouse ? mouseClickThreshold : touchClickThreshold;則不響應OnClick事件

下面這部分代碼是當響應了OnDrag事件就把currentTouch.clickNotification = ClickNotification.None;就不在會響應OnClick事件了。

C#代碼  收藏代碼
  1.                                           bool isDisabled = (currentTouch.clickNotification == ClickNotification.None);  
  2. Notify(currentTouch.dragged, "OnDrag", currentTouch.delta);  
  3. isDragging = false;  
  4.   
  5. if (isDisabled)  
  6. {  
  7.     // If the notification status has already been disabled, keep it as such  
  8.     currentTouch.clickNotification = ClickNotification.None;  
  9. }  
  10. else if (currentTouch.clickNotification == ClickNotification.BasedOnDelta && click < mag)  
  11. {  
  12.     // We've dragged far enough to cancel the click  
  13.     currentTouch.clickNotification = ClickNotification.None;  
  14. }  

 然後再執行OnClick和OnDoubleClick事件先判斷條件currentTouch.clickNotification != ClickNotification.None 是否成立:

C#代碼  收藏代碼
  1. // If the touch should consider clicks, send out an OnClick notification  
  2.                     if (currentTouch.clickNotification != ClickNotification.None)  
  3.                     {  
  4.                         float time = Time.realtimeSinceStartup;  
  5.   
  6.                         Notify(currentTouch.pressed, "OnClick"null);  
  7.   
  8.                         if (currentTouch.clickTime + 0.35f > time)  
  9.                         {  
  10.                             Notify(currentTouch.pressed, "OnDoubleClick"null);  
  11.                         }  
  12.                         currentTouch.clickTime = time;  
  13.                     }  

 EventType

C#代碼  收藏代碼
  1. public enum EventType  
  2.     {  
  3.         World,  // Perform a Physics.Raycast and sort by distance to the point that was hit.  
  4.         UI,     // Perform a Physics.Raycast and sort by widget depth.  
  5.     }  

        這個很簡單就是定義當前射線和碰撞體碰撞的判斷標準,如果是UI則以Depth來判斷,如果是World是以實際距離來判斷。

List<UICamera> list

C#代碼  收藏代碼
  1.        /// <summary>  
  2. /// List of all active cameras in the scene.  
  3. /// </summary>  
  4.   
  5. static public List<UICamera> list = new List<UICamera>();  

        UICamera在初始化的時候會被加入 mList這個鏈表中,然後對鏈表進行排序,根據相機的深度,深度值越小的相機排位靠前,最靠前的相機爲場景的主UICamera,然後只有只有主UICamera纔會去監測場景中的事件,其他的UICamera並不執行監測任務。UICamera利用Unity的Raycast去監測事件發生的對象,因爲發射出去的Ray對象必須碰撞到Collider纔會有反應,所以NGUI中所有需要響應事件的控件均需要添加Collider,同時Ray只會碰撞到深度最小的Collider,Ray射線的最大深度爲rangeDistance,當這個值爲-1時則發射深度和相機深度一樣,主UICamera每一幀都會主動去發射Ray檢測鼠標此時觸碰到的對象並將其記錄在對應的鼠標按鍵事件中,這是能監測到OnHover這個動作的關鍵(當然只有在useMouse爲true時纔會有此操作)。

        在遊戲場景初始化階段,每個UICamera都會根據平臺義useMouse、useTouch、useKeyboard和useController 這些屬性,分別對應的是能否在場景使用鼠標、觸摸屏、鍵盤以及搖桿。

C#代碼  收藏代碼
  1.        public bool useMouse = true;  
  2.   
  3. public bool useTouch = true;  
  4.   
  5. public bool allowMultiTouch = true;  
  6.   
  7. public bool useKeyboard = true;  
  8.   
  9. public bool useController = true;  

 

MouseOrTouch

C#代碼  收藏代碼
  1.        /// <summary>  
  2. /// Ambiguous mouse, touch, or controller event.  
  3. /// </summary>  
  4.   
  5. public class MouseOrTouch  
  6. {  
  7.     public Vector2 pos;             // Current position of the mouse or touch event  
  8.     public Vector2 delta;           // Delta since last update  
  9.     public Vector2 totalDelta;      // Delta since the event started being tracked  
  10.   
  11.     public Camera pressedCam;       // Camera that the OnPress(true) was fired with  
  12.   
  13.     public GameObject current;      // The current game object under the touch or mouse  
  14.     public GameObject pressed;      // The last game object to receive OnPress  
  15.     public GameObject dragged;      // The last game object to receive OnDrag  
  16.   
  17.     public float clickTime = 0f;    // The last time a click event was sent out  
  18.   
  19.     public ClickNotification clickNotification = ClickNotification.Always;  
  20.     public bool touchBegan = true;  
  21.     public bool pressStarted = false;  
  22.     public bool dragStarted = false;  
  23. }  

       MouseOrTouch是一個很重要的類,是一個事件的結構體,然後就定義了不同平臺的事件,記錄Camera監測的事件:MouseOrTouch只是記錄“鼠標”等的移動的“物理”信息——位置,移動距離等,只有鼠標是否按下只有在Update中每幀監測。

       下面定義不同平臺的事件,例如鼠標事件,mMouse記錄鼠標左鍵,右鍵和中鍵的事件(因爲鼠標這裏只記錄鼠標的三個按鍵,所以mMouse纔是有三個元素,現在明白爲啥了吧)。

C#代碼  收藏代碼
  1.        // Mouse events  
  2. static MouseOrTouch[] mMouse = new MouseOrTouch[] { new MouseOrTouch(), new MouseOrTouch(), new MouseOrTouch() };  
  3.   
  4. // The last object to receive OnHover  
  5. static GameObject mHover;  
  6.   
  7. // Joystick/controller/keyboard event  
  8. static MouseOrTouch mController = new MouseOrTouch();  
  9.   
  10. // Used to ensure that joystick-based controls don't trigger that often  
  11. static float mNextEvent = 0f;  
  12.   
  13. // List of currently active touches  
  14. static Dictionary<int, MouseOrTouch> mTouches = new Dictionary<int, MouseOrTouch>();  

 currentTouch

C#代碼  收藏代碼
  1.        /// <summary>  
  2. /// ID of the touch or mouse operation prior to sending out the event. Mouse ID is '-1' for left, '-2' for right mouse button, '-3' for middle.  
  3. /// </summary>  
  4.   
  5. static public int currentTouchID = -1;  
  6.   
  7. /// <summary>  
  8. /// Current touch, set before any event function gets called.  
  9. /// </summary>  
  10.   
  11. static public MouseOrTouch currentTouch = null;  

       currentTouch這個變量是整個UICamera中控制事件監測的關鍵所在,記錄了當前事件的觸發對象和一些其他諸如position位置、dealta時間、totaldealta總時間等屬性,然後用currentTouchID記錄當前事件的類型,這些類型包括鼠標事件、鍵盤控制器事件以及觸摸屏事件。

 

ProcessTouch

       ProcessTouch這個函數就是根據currentTouch來針對不同的情況響應不同的函數,被ProcessMouse,ProcessTouch和ProcessOthers調用,如ProcessMouse,分別捕獲鼠標三個按鍵的狀態,然後調用ProcessTouch來響應:

C#代碼  收藏代碼
  1.               // Process all 3 mouse buttons as individual touches  
  2. if (useMouse)  
  3. {  
  4.     for (int i = 0; i < 3; ++i)  
  5.     {  
  6.         bool pressed = Input.GetMouseButtonDown(i);  
  7.         bool unpressed = Input.GetMouseButtonUp(i);  
  8.   
  9.         currentTouch = mMouse[i];  
  10.         currentTouchID = -1 - i;  
  11.   
  12.         // We don't want to update the last camera while there is a touch happening  
  13.         if (pressed) currentTouch.pressedCam = currentCamera;  
  14.         else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;  
  15.   
  16.         // Process the mouse events  
  17.         ProcessTouch(pressed, unpressed);  
  18.     }  

『ProcessMouse分析

         因爲之前版本升級到NGUI3.0.6時,UICamera出現了一個Bug:當Time.ScaleTime != 1f 的時候,事件響應有問題,當時由於時間關係,只是和之前的版本進行比對,增加了些代碼解決的。但是還是感覺沒有能對UICamera具體細節沒能完全掌握,挺蹩腳的,還不能達到“自主”的處理目的,所以一直都想有時間好好把UICamera的事件分發流程細節清理下。

C#代碼  收藏代碼
  1.        /// <summary>  
  2. /// Update mouse input.  
  3. /// </summary>  
  4.   
  5. public void ProcessMouse ()  
  6. {  
  7.     // No need to perform raycasts every frame  
  8.     if (mNextRaycast < RealTime.time)     //更新鼠標current爲當前的 hoveredObject,如果時間間隔小於 20毫秒,就不更新  
  9.     {  
  10.         mNextRaycast = RealTime.time + 0.02f;  
  11.         if (!Raycast(Input.mousePosition, out lastHit)) hoveredObject = fallThrough;  
  12.         if (hoveredObject == null) hoveredObject = genericEventHandler;  
  13.         for (int i = 0; i < 3; ++i) mMouse[i].current = hoveredObject;  
  14.     }  
  15.   
  16.     lastTouchPosition = Input.mousePosition;  
  17.     bool highlightChanged = (mMouse[0].last != mMouse[0].current);  
  18.     if (highlightChanged) currentScheme = ControlScheme.Mouse;  
  19.   
  20.     // Update the position and delta                  更新三個鼠標按鍵的位置 delta 和pos ,  
  21.     mMouse[0].delta = lastTouchPosition - mMouse[0].pos;  
  22.     mMouse[0].pos = lastTouchPosition;  
  23.     bool posChanged = mMouse[0].delta.sqrMagnitude > 0.001f;  
  24.   
  25.     // Propagate the updates to the other mouse buttons  
  26.     for (int i = 1; i < 3; ++i)      
  27.     {  
  28.         mMouse[i].pos = mMouse[0].pos;  
  29.         mMouse[i].delta = mMouse[0].delta;  
  30.     }  
  31.   
  32.     // Is any button currently pressed?  
  33.     bool isPressed = false;  
  34.   
  35.     for (int i = 0; i < 3; ++i)  
  36.     {  
  37.         if (Input.GetMouseButton(i))  
  38.         {  
  39.             currentScheme = ControlScheme.Mouse;  
  40.             isPressed = true;  
  41.             break;  
  42.         }  
  43.     }  
  44.   
  45.     if (isPressed)  
  46.     {  
  47.         // A button was pressed -- cancel the tooltip  
  48.         mTooltipTime = 0f;  
  49.     }  
  50.     else if (posChanged && (!stickyTooltip || highlightChanged))    //更新Tip顯示  
  51.     {  
  52.         if (mTooltipTime != 0f)  
  53.         {  
  54.             // Delay the tooltip  
  55.             mTooltipTime = RealTime.time + tooltipDelay;  
  56.         }  
  57.         else if (mTooltip != null)  
  58.         {  
  59.             // Hide the tooltip  
  60.             ShowTooltip(false);  
  61.         }  
  62.     }  
  63.   
  64.     // The button was released over a different object -- remove the highlight from the previous  
  65.     if (!isPressed && mHover != null && highlightChanged)   //更新hover GameObject ,並分發 OnHover事件  
  66.     {  
  67.         currentScheme = ControlScheme.Mouse;  
  68.         if (mTooltip != null) ShowTooltip(false);  
  69.         Notify(mHover, "OnHover"false);  
  70.         mHover = null;  
  71.     }  
  72.   
  73.     // Process all 3 mouse buttons as individual touches   分別處理鼠標的三個按鍵,獲取按鍵狀態,進行事件分發  
  74.     for (int i = 0; i < 3; ++i)  
  75.     {  
  76.         bool pressed = Input.GetMouseButtonDown(i);  
  77.         bool unpressed = Input.GetMouseButtonUp(i);  
  78.   
  79.         if (pressed || unpressed) currentScheme = ControlScheme.Mouse;  
  80.   
  81.         currentTouch = mMouse[i];  
  82.         currentTouchID = -1 - i;  
  83.         currentKey = KeyCode.Mouse0 + i;  
  84.   
  85.         // We don't want to update the last camera while there is a touch happening  
  86.         if (pressed) currentTouch.pressedCam = currentCamera;  
  87.         else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;  
  88.   
  89.         // Process the mouse events  
  90.         ProcessTouch(pressed, unpressed);  
  91.         currentKey = KeyCode.None;  
  92.     }  
  93.     currentTouch = null;  
  94.   
  95.     // If nothing is pressed and there is an object under the touch, highlight it  
  96.     if (!isPressed && highlightChanged)  
  97.     {  
  98.         currentScheme = ControlScheme.Mouse;  
  99.         mTooltipTime = RealTime.time + tooltipDelay;  
  100.         mHover = mMouse[0].current;  
  101.         Notify(mHover, "OnHover"true);  
  102.     }  
  103.   
  104.     // Update the last value  
  105.     mMouse[0].last = mMouse[0].current;  
  106.     for (int i = 1; i < 3; ++i) mMouse[i].last = mMouse[0].last;  
  107. }  

  這次回看UICamera的代碼,更加UICamera優化了很多,代碼邏輯清晰簡單了,之前的一直感覺很亂(一堆條件判斷)才一直沒有細看。雖然上面代碼還是有加點註釋,其實已經完全沒必要了。然後在NGUI3.0.7版本還增加了 在Editor下用鼠標做屏幕Touch的操作的功能:

/// Process fake touch events where the mouse acts as a touch device.

/// Useful for testing mobile functionality in the editor.

                                                                                                                                                                                                                    增補於 2013,12,29 15:15

其他

        UICamera還提供其他一些“特性”,能夠讓開發者實現更多的功能(就不解釋了吧, 有註釋):

C#代碼  收藏代碼
  1. /// <summary>  
  2.     /// If 'true', once a press event is started on some object, that object will be the only one that will be  
  3.     /// receiving future events until the press event is finally released, regardless of where that happens.  
  4.     /// If 'false', the press event won't be locked to the original object, and other objects will be receiving  
  5.     /// OnPress(true) and OnPress(false) events as the touch enters and leaves their area.  
  6.     /// </summary>  
  7.   
  8.     public bool stickyPress = true;  
  9.   
  10. /// <summary>  
  11.     /// If set, this game object will receive all events regardless of whether they were handled or not.  
  12.     /// </summary>  
  13.   
  14.     static public GameObject genericEventHandler;  
  15.   
  16.     /// <summary>  
  17.     /// If events don't get handled, they will be forwarded to this game object.  
  18.     /// </summary>  
  19.   
  20.     static public GameObject fallThrough;  

 

最後,NGUI一共支持一下事件:

 

C#代碼  收藏代碼
  1. void OnHover (bool isOver) – Sent out when the mouse hovers over the collider or moves away from it. Not sent on touch-based devices.  
  2. void OnPress (bool isDown) – Sent when a mouse button (or touch event) gets pressed over the collider (with ‘true’) and when it gets released (with ‘false’, sent to the same collider even if it’s released elsewhere).  
  3. void OnClick() — Sent to a mouse button or touch event gets released on the same collider as OnPress. UICamera.currentTouchID tells you which button was clicked.  
  4. void OnDoubleClick () — Sent when the click happens twice within a fourth of a second. UICamera.currentTouchID tells you which button was clicked.  
  5. void OnSelect (bool selected) – Same as OnClick, but once a collider is selected it will not receive any further OnSelect events until you select some other collider.  
  6. void OnDrag (Vector2 delta) – Sent when the mouse or touch is moving in between of OnPress(true) and OnPress(false).  
  7. void OnDrop (GameObject drag) – Sent out to the collider under the mouse or touch when OnPress(falseis called over a different collider than triggered the OnPress(trueevent. The passed parameter is the game object of the collider that received the OnPress(trueevent.  
  8. void OnInput (string text) – Sent to the same collider that received OnSelect(true) message after typing something. You likely won’t need this, but it’s used by UIInput  
  9. void OnTooltip (bool show) – Sent after the mouse hovers over a collider without moving for longer than tooltipDelay, and when the tooltip should be hidden. Not sent on touch-based devices.  
  10. void OnScroll (float delta) is sent out when the mouse scroll wheel is moved.  
  11. void OnKey (KeyCode key) is sent when keyboard or controller input is used.  

 

小結:

       最近由於項目要用到FastGUI,然後手上的FastGUI不支持NGUI(NGUI變動太大了),然後自己要升級下FastGUI,就要更多的掌握NGUI的原理,所以纔會一直不斷的寫一些文章。寫文章主要是記錄下自己從中看到的東西,當然D.S.Qiu最喜歡和大家分享,希望能對讀者有幫助,哪怕只有一個人,D.S.Qiu也會很興奮的,因爲很多次D.S.Qiu都不打算寫的(文章寫的太爛,沒有深度,邏輯差,每次都要熬夜等),但當我看到別人文章的亮點時,我就覺得自己還是可以分享些的。

       今天把FastGUI 兼容到了NGUI3.0.3f,還增加一些功能,然後要寫一個文檔給美術的同事,我感覺頭就大了,感覺如果要我口述一定能讓聽者完全明白,但是寫起來就完全不着調,所以覺得D.S.Qiu的文字很渣,馬上就是凌晨1:30,睡覺,晚安!

 

        如果您對D.S.Qiu有任何建議或意見可以在文章後面評論,或者發郵件([email protected])交流,您的鼓勵和支持是我前進的動力,希望能有更多更好的分享。

        轉載請在文首註明出處:http://dsqiu.iteye.com/blog/1971866

更多精彩請關注D.S.Qiu的博客和微博(ID:靜水逐風)

 

 

參考:

2B青年: http://blog.sina.com.cn/s/blog_6f16aba701017mgz.html

②tasharen: http://www.tasharen.com/?page_id=160

雨鬆MOMOhttp://www.xuanyusong.com/archives/2390

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章