NGUI所見(jiàn)即所得之UICamera
UI,除了界面的顯示,還有一個(gè)重要的元素:事件響應(yīng)。MoneBehaviour提供一些事件提供了一些函數(shù)接口(OnMouseUp,OnMouseDown等),只要MonBehaviour的子類實(shí)現(xiàn)這相應(yīng)的方法以及方法執(zhí)行的條件達(dá)到,Unity底層就會(huì)分發(fā)調(diào)用執(zhí)行這個(gè)函數(shù)。一般地,UI事件響應(yīng)處理機(jī)制會(huì)有4個(gè)基本元素:
1.event object:事件對(duì)象,即當(dāng)前事件的類型,如鼠標(biāo)左鍵按下等。
2.event source:事件源,或事件的觸發(fā)器,比如說(shuō),鼠標(biāo)左鍵單擊點(diǎn)擊一個(gè)button,那么button就是event source,鼠標(biāo)左鍵單擊就是event source。
3.event handle:事件處理方法。
4.event listener:事件的監(jiān)聽(tīng),比如上面說(shuō)的鼠標(biāo)左鍵點(diǎn)擊一個(gè)button,event listener就是監(jiān)聽(tīng)打button的一個(gè)mouse click事件,然后分發(fā)調(diào)用對(duì)應(yīng)的event handle進(jìn)行處理。
一般event source不會(huì)單獨(dú)存在,經(jīng)常會(huì)跟event handle綁定在一起,其實(shí)就是指定了不同的event handle就是不同event source,如Button就有單擊雙擊事件,Input就有輸入焦點(diǎn)事件,event listener就好像人的大腦,監(jiān)控這個(gè)所有的事件,同時(shí)作出不同的響應(yīng)。
NGUI自己組織了一套UI事件響應(yīng)處理機(jī)制, 不是對(duì)MonoBehaviour的方法的封裝調(diào)用,UICamera就是NGUI框架中的event listener,原理很簡(jiǎn)單:在Update中捕獲鼠標(biāo),鍵盤等設(shè)備的輸入(也可以狹義的認(rèn)為UICamera就是event listener),判斷不同event object 和event source,然后“廣播”分發(fā)執(zhí)行event handle,下面附上分發(fā)的函數(shù):
- /// <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 (go != null)
- {
- go.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);
-
- if (genericEventHandler != null && genericEventHandler != go)
- {
- genericEventHandler.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);
- }
- }
- }
知道的誰(shuí)是event listener,那要怎么實(shí)現(xiàn)event handle。實(shí)現(xiàn)NGUI的事件的方法有很多種,③給了三種方式監(jiān)聽(tīng)NGUI的事件方法。文末貼了NGUI支持的event handle。
增補(bǔ)于:11/10/2013 8:45,感覺(jué)之前的沒(méi)頭沒(méi)尾的,就加了上面的鋪墊
記得剛開(kāi)始用NGUI的時(shí)候,就有心思要去琢磨下UICamera,那個(gè)時(shí)候NGUI還是2.6的版本,現(xiàn)在已經(jīng)到了3.0.3f,NGUI更新真的很強(qiáng)勁, 當(dāng)然改動(dòng)也挺大的,特性也越來(lái)越多了。之前本來(lái)研究下UICamera最后還是放棄了,因?yàn)閁ICamera的代碼太復(fù)雜了,很凌亂,也就放下去了,這幾天重新翻看了下,發(fā)現(xiàn)UICamera的可讀性太強(qiáng)了,代碼的組織邏輯很強(qiáng),完全可以當(dāng)做文本來(lái)從上到下來(lái)閱讀,所以才會(huì)有這篇文章。
UICamera做了很多有優(yōu)化,新增了一些特性,之前可能覺(jué)得NGUI只是做一個(gè)工具,現(xiàn)在越來(lái)越完美了,少?gòu)U話,下面把看到的亮點(diǎn)呈上。
ClickNotification
- /// <summary>
- /// Whether the touch event will be sending out the OnClick notification at the end.
- /// </summary>
-
- public enum ClickNotification
- {
- None,
- Always,
- BasedOnDelta,
- }
ClickNotification定義了OnClick響應(yīng)的條件,后面也定義了ClickNotification變量 public ClickNotification clickNotification = ClickNotification.Always;
ClickNotification.None: 不響應(yīng)OnClick事件
ClickNotification.Always:總是響應(yīng)OnClick事件
ClickNotification.BaseOnDelta:依據(jù)移動(dòng)的delta的距離判斷是否響應(yīng)OnClick函數(shù),如果移動(dòng)距離大于float click = isMouse ? mouseClickThreshold : touchClickThreshold;則不響應(yīng)OnClick事件
下面這部分代碼是當(dāng)響應(yīng)了OnDrag事件就把currentTouch.clickNotification = ClickNotification.None;就不在會(huì)響應(yīng)OnClick事件了。
- bool isDisabled = (currentTouch.clickNotification == ClickNotification.None);
- Notify(currentTouch.dragged, "OnDrag", currentTouch.delta);
- 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;
- }
然后再執(zhí)行OnClick和OnDoubleClick事件先判斷條件currentTouch.clickNotification != ClickNotification.None 是否成立:
- // If the touch should consider clicks, send out an OnClick notification
- if (currentTouch.clickNotification != ClickNotification.None)
- {
- float time = Time.realtimeSinceStartup;
-
- Notify(currentTouch.pressed, "OnClick", null);
-
- if (currentTouch.clickTime + 0.35f > time)
- {
- Notify(currentTouch.pressed, "OnDoubleClick", null);
- }
- currentTouch.clickTime = time;
- }
EventType
- public enum EventType
- {
- World, // Perform a Physics.Raycast and sort by distance to the point that was hit.
- UI, // Perform a Physics.Raycast and sort by widget depth.
- }
這個(gè)很簡(jiǎn)單就是定義當(dāng)前射線和碰撞體碰撞的判斷標(biāo)準(zhǔn),如果是UI則以Depth來(lái)判斷,如果是World是以實(shí)際距離來(lái)判斷。
List<UICamera> list
- /// <summary>
- /// List of all active cameras in the scene.
- /// </summary>
-
- static public List<UICamera> list = new List<UICamera>();
UICamera在初始化的時(shí)候會(huì)被加入 mList這個(gè)鏈表中,然后對(duì)鏈表進(jìn)行排序,根據(jù)相機(jī)的深度,深度值越小的相機(jī)排位靠前,最靠前的相機(jī)為場(chǎng)景的主UICamera,然后只有只有主UICamera才會(huì)去監(jiān)測(cè)場(chǎng)景中的事件,其他的UICamera并不執(zhí)行監(jiān)測(cè)任務(wù)。UICamera利用Unity的Raycast去監(jiān)測(cè)事件發(fā)生的對(duì)象,因?yàn)榘l(fā)射出去的Ray對(duì)象必須碰撞到Collider才會(huì)有反應(yīng),所以NGUI中所有需要響應(yīng)事件的控件均需要添加Collider,同時(shí)Ray只會(huì)碰撞到深度最小的Collider,Ray射線的最大深度為rangeDistance,當(dāng)這個(gè)值為-1時(shí)則發(fā)射深度和相機(jī)深度一樣,主UICamera每一幀都會(huì)主動(dòng)去發(fā)射Ray檢測(cè)鼠標(biāo)此時(shí)觸碰到的對(duì)象并將其記錄在對(duì)應(yīng)的鼠標(biāo)按鍵事件中,這是能監(jiān)測(cè)到OnHover這個(gè)動(dòng)作的關(guān)鍵(當(dāng)然只有在useMouse為true時(shí)才會(huì)有此操作)。
在游戲場(chǎng)景初始化階段,每個(gè)UICamera都會(huì)根據(jù)平臺(tái)義useMouse、useTouch、useKeyboard和useController 這些屬性,分別對(duì)應(yīng)的是能否在場(chǎng)景使用鼠標(biāo)、觸摸屏、鍵盤以及搖桿。
- public bool useMouse = true;
-
- public bool useTouch = true;
-
- public bool allowMultiTouch = true;
-
- public bool useKeyboard = true;
-
- public bool useController = true;
MouseOrTouch
- /// <summary>
- /// Ambiguous mouse, touch, or controller event.
- /// </summary>
-
- public class MouseOrTouch
- {
- public Vector2 pos; // Current 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 current; // The current game object under the touch or mouse
- public GameObject pressed; // The last game object to receive OnPress
- public GameObject dragged; // The last game object to receive OnDrag
-
- 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;
- }
MouseOrTouch是一個(gè)很重要的類,是一個(gè)事件的結(jié)構(gòu)體,然后就定義了不同平臺(tái)的事件,記錄Camera監(jiān)測(cè)的事件:MouseOrTouch只是記錄“鼠標(biāo)”等的移動(dòng)的“物理”信息——位置,移動(dòng)距離等,只有鼠標(biāo)是否按下只有在Update中每幀監(jiān)測(cè)。
下面定義不同平臺(tái)的事件,例如鼠標(biāo)事件,mMouse記錄鼠標(biāo)左鍵,右鍵和中鍵的事件(因?yàn)槭髽?biāo)這里只記錄鼠標(biāo)的三個(gè)按鍵,所以mMouse才是有三個(gè)元素,現(xiàn)在明白為啥了吧)。
- // Mouse events
- static MouseOrTouch[] mMouse = new MouseOrTouch[] { new MouseOrTouch(), new MouseOrTouch(), new MouseOrTouch() };
-
- // The last object to receive OnHover
- static GameObject mHover;
-
- // Joystick/controller/keyboard event
- static MouseOrTouch mController = new MouseOrTouch();
-
- // Used to ensure that joystick-based controls don't trigger that often
- static float mNextEvent = 0f;
-
- // List of currently active touches
- static Dictionary<int, MouseOrTouch> mTouches = new Dictionary<int, MouseOrTouch>();
currentTouch
- /// <summary>
- /// 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.
- /// </summary>
-
- static public int currentTouchID = -1;
-
- /// <summary>
- /// Current touch, set before any event function gets called.
- /// </summary>
-
- static public MouseOrTouch currentTouch = null;
currentTouch這個(gè)變量是整個(gè)UICamera中控制事件監(jiān)測(cè)的關(guān)鍵所在,記錄了當(dāng)前事件的觸發(fā)對(duì)象和一些其他諸如position位置、dealta時(shí)間、totaldealta總時(shí)間等屬性,然后用currentTouchID記錄當(dāng)前事件的類型,這些類型包括鼠標(biāo)事件、鍵盤控制器事件以及觸摸屏事件。
ProcessTouch
ProcessTouch這個(gè)函數(shù)就是根據(jù)currentTouch來(lái)針對(duì)不同的情況響應(yīng)不同的函數(shù),被ProcessMouse,ProcessTouch和ProcessOthers調(diào)用,如ProcessMouse,分別捕獲鼠標(biāo)三個(gè)按鍵的狀態(tài),然后調(diào)用ProcessTouch來(lái)響應(yīng):
- // Process all 3 mouse buttons as individual touches
- if (useMouse)
- {
- for (int i = 0; i < 3; ++i)
- {
- bool pressed = Input.GetMouseButtonDown(i);
- bool unpressed = Input.GetMouseButtonUp(i);
-
- currentTouch = mMouse[i];
- currentTouchID = -1 - i;
-
- // We don't want to update the last camera while there is a touch happening
- if (pressed) currentTouch.pressedCam = currentCamera;
- else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;
-
- // Process the mouse events
- ProcessTouch(pressed, unpressed);
- }
『ProcessMouse分析
因?yàn)橹鞍姹旧?jí)到NGUI3.0.6時(shí),UICamera出現(xiàn)了一個(gè)Bug:當(dāng)Time.ScaleTime != 1f 的時(shí)候,事件響應(yīng)有問(wèn)題,當(dāng)時(shí)由于時(shí)間關(guān)系,只是和之前的版本進(jìn)行比對(duì),增加了些代碼解決的。但是還是感覺(jué)沒(méi)有能對(duì)UICamera具體細(xì)節(jié)沒(méi)能完全掌握,挺蹩腳的,還不能達(dá)到“自主”的處理目的,所以一直都想有時(shí)間好好把UICamera的事件分發(fā)流程細(xì)節(jié)清理下。
這次回看UICamera的代碼,更加UICamera優(yōu)化了很多,代碼邏輯清晰簡(jiǎn)單了,之前的一直感覺(jué)很亂(一堆條件判斷)才一直沒(méi)有細(xì)看。雖然上面代碼還是有加點(diǎn)注釋,其實(shí)已經(jīng)完全沒(méi)必要了。然后在NGUI3.0.7版本還增加了 在Editor下用鼠標(biāo)做屏幕Touch的操作的功能:
/// Process fake touch events where the mouse acts as a touch device.
/// Useful for testing mobile functionality in the editor.』
增補(bǔ)于 2013,12,29 15:15
其他
UICamera還提供其他一些“特性”,能夠讓開(kāi)發(fā)者實(shí)現(xiàn)更多的功能(就不解釋了吧, 有注釋):
- /// <summary>
- /// If 'true', once a press event is started on some object, that object will be the only one that will be
- /// receiving future events until the press event is finally released, regardless of where that happens.
- /// If 'false', the press event won't be locked to the original object, and other objects will be receiving
- /// OnPress(true) and OnPress(false) events as the touch enters and leaves their area.
- /// </summary>
-
- public bool stickyPress = true;
-
- /// <summary>
- /// If set, this game object will receive all events regardless of whether they were handled or not.
- /// </summary>
-
- static public GameObject genericEventHandler;
-
- /// <summary>
- /// If events don't get handled, they will be forwarded to this game object.
- /// </summary>
-
- static public GameObject fallThrough;
最后,NGUI一共支持一下事件:
- void OnHover (bool isOver) – Sent out when the mouse hovers over the collider or moves away from it. Not sent on touch-based devices.
- 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).
- 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.
- void OnDoubleClick () — Sent when the click happens twice within a fourth of a second. UICamera.currentTouchID tells you which button was clicked.
- 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.
- void OnDrag (Vector2 delta) – Sent when the mouse or touch is moving in between of OnPress(true) and OnPress(false).
- void OnDrop (GameObject drag) – Sent out to the collider under the mouse or touch when OnPress(false) is called over a different collider than triggered the OnPress(true) event. The passed parameter is the game object of the collider that received the OnPress(true) event.
- 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
- 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.
- void OnScroll (float delta) is sent out when the mouse scroll wheel is moved.
- void OnKey (KeyCode key) is sent when keyboard or controller input is used.
小結(jié):
最近由于項(xiàng)目要用到FastGUI,然后手上的FastGUI不支持NGUI(NGUI變動(dòng)太大了),然后自己要升級(jí)下FastGUI,就要更多的掌握NGUI的原理,所以才會(huì)一直不斷的寫一些文章。寫文章主要是記錄下自己從中看到的東西,當(dāng)然D.S.Qiu最喜歡和大家分享,希望能對(duì)讀者有幫助,哪怕只有一個(gè)人,D.S.Qiu也會(huì)很興奮的,因?yàn)楹芏啻蜠.S.Qiu都不打算寫的(文章寫的太爛,沒(méi)有深度,邏輯差,每次都要熬夜等),但當(dāng)我看到別人文章的亮點(diǎn)時(shí),我就覺(jué)得自己還是可以分享些的。
今天把FastGUI 兼容到了NGUI3.0.3f,還增加一些功能,然后要寫一個(gè)文檔給美術(shù)的同事,我感覺(jué)頭就大了,感覺(jué)如果要我口述一定能讓聽(tīng)者完全明白,但是寫起來(lái)就完全不著調(diào),所以覺(jué)得D.S.Qiu的文字很渣,馬上就是凌晨1:30,睡覺(jué),晚安!
如果您對(duì)D.S.Qiu有任何建議或意見(jiàn)可以在文章后面評(píng)論,或者發(fā)郵件(gd.s.qiu@gmail.com)交流,您的鼓勵(lì)和支持是我前進(jìn)的動(dòng)力,希望能有更多更好的分享。
轉(zhuǎn)載請(qǐng)?jiān)谖氖鬃⒚鞒鎏帲?a style="line-height: 1.5;" >http://dsqiu.iteye.com/blog/1971866
更多精彩請(qǐng)關(guān)注D.S.Qiu的博客和微博(ID:靜水逐風(fēng))
參考:
①2B青年: http://blog.sina.com.cn/s/blog_6f16aba701017mgz.html
②tasharen: http://www.tasharen.com/?page_id=160
③雨松MOMO:http://www.xuanyusong.com/archives/2390