Unity事件系統EventSystem簡析
相關元件和類
EventSystem
1.負責InputModule的切換(因為現在遊戲大部分都只有一個StanaloneInputModule,所以切換這部分可以先不考慮)。
2.負責InputModule的啟用與反啟用。
3.負責Tick整個事件系統。
4.更新InputModule,處理失焦和記錄滑鼠位置。
5.記錄一個Selected物件。
StandaloneInputModule
1.處理輸入的滑鼠或觸控事件,進行事件的分發。
2.啟用和反啟用時負責初始化(選擇物件,滑鼠位置)和清理無效資料(選擇物件、pointerData)。
3.不直接使用Input獲取資料,而使用一個MonoBehaviour進行封裝,提供切換Input的能力(例如遊戲進入了反轉模式,點左下角時希望右上角有反應。那麼重寫一個對應的指令碼,在進入這個模式時切換Input指令碼就可以)。
Raycaster
1.找到所有被射線檢測成功的物件,選排序後第一個物件進行事件分發。
Input類
1.負責獲取和封裝外部的輸入資訊,如點選、重力感應等。
2.BaseInput提供和Input類一樣的能力,是對Input物件的封裝,介面名字都一樣,方便輸入系統的切換。
Touch類
1.Touch類是一個Touch行為(在螢幕上按下,抬起的過程算一個Touch行為)某一時刻的資料。
2.Touch類包含的資訊。
主要分3部分:
<1>每一個Touch行為從開始到結束,有一個唯一Id
<2>當前這個Touch行為所處在的階段,一共5個階段
public enum TouchPhase { Began = 0,//按下 Moved = 1,//正在移動 Stationary = 2,//靜止,但沒有結束 Ended = 3,//離開 Canceled = 4//黑屏等其他因素導致的結束 }
<3>當前位置,移動距離等資訊。
3.Touch行為在絕大多數情況下都是由Began開始,Ended或Canceled結束。
但是我們並沒有監聽Touch階段修改的能力(沒找到相關的介面),只能通過Input.GetTouch介面在某一時間點(如update中)來迴圈獲取Touch資訊。然後通過FingerId,phase來還原一個完整的Touch行為。但這樣會有一個問題,通過GetTouch獲取的Touch資訊可能是不完整的,如:
1.在一個Touch拖動的過程中開始迴圈呼叫GetTouch,那麼我們得到的Touch就會不是由Began開始的,而是由Moved開始的。
2.在幀數很低,且在一幀內連續點選多次時,可能出現相同FingerId的Touch沒有通過Ended結束,然後又直接Began的情況。
所以在將GetTouch獲取的資料作為EventSystem的輸入資料時,需要將這些特殊情況考慮進去。
執行流程
總體流程
一次StandaloneInputModule.Process處理流程
以Touch舉例
tips:
1.PointerEventData可以理解為對Touch行為的進一步封裝,記錄了Touch行為資訊,如開始位置等,且在此基礎上增加了射線檢測結果等資訊。每一個PointerEventData的生命週期基本上和Touch行為相同。由pressed開始(對應Touch的Began,如快取中沒有對應fingerId的PointerEventData,則新建一個),released結束(對應Touch的Ended或Canceled,從快取中移除該PointerEventData)。當然對於特殊情況要特殊處理(如上面提到的沒有由Began開始的Touch等)。
2.Process主要的工作就是維護PointerEventData的資料,同時根據PointerEventData發出事件。
3.對事件指令碼的查詢是向上查詢的,如C是B的子節點,B是A的子節點。射線檢測的結果是C。那麼會按C->B->A的順序去查詢可響應該事件的物件。
射線檢測流程
這裡簡單說一下GraphicRaycaster作為舉例。
GraphicRaycaster的射線檢測
1.GraphicRaycaster是檢測同gameobject下canvas中包含的所有Graphic元素是否被射線擊中的指令碼。
2.Graphic在Onenable,OnDisable,OnBeforeTransformParentChanged,OnTransformParentChanged,OnCanvasHierarchyChanged這幾個時間點把自己加入或移除一個以canvas為鍵值的graphic集合的字典中。
3.具體檢測過程:
<1>先從快取中獲取該Canvas下所有的Graphic物件。
<2>處理多顯示器問題,先做一波座標轉換。
<3>根據BlockingObjects,對遊戲中的3D或2D物件做一次射線檢測,儲存離相機最近的物件的距離,之後用於對結果的過濾。
<4>先通過RectangleContainsScreenPoint判斷射線擊中點是否在Graphic的RectTransform中,再通過Graphic自身的Raycast函式進行進一步的檢測(檢測CanvasGroup,Active狀態等)。
<5>最後再做一些測試,如反轉剔除,遮擋測試等。
射線檢測及排序
1.遊戲中所有的Raycaster都進行一次射線檢測,獲取當前射線擊中的所有物體,統一進行排序,選排序後的第一個物件作為射線檢測的結果。
2.排序規則
不同Racaster下:
camera.depth
Raycaster.sortOrderPriority 針對ScreenSpaceOverlay
Raycaster.renderOrderPriority 針對ScreenSpaceOverlay
相同Racaster下:
sortingLayer
sortingOrder
depth
distance
index
舉例
在手機上按這個方式操作。
其中A上的指令碼繼承了IPointerEnterHandler,IPointerExitHandler,IPointerDownHandler,IPointerUpHandler,IPointerClickHandler,IDragHandler介面。
B上的指令碼繼承了IPointerEnterHandler,IPointerExitHandler, IDropHandler介面。
這一系列操作中事件的觸發主要依賴於對這幾個變數的設定和判定。
①pointerPress:按下時射線擊中物件或向上查詢的某一掛有繼承了IPointerDownHandler或IPointerClickHandler指令碼的物件。
②pointerDrag:按下時射線擊中物件或向上查詢的某一掛有繼承了IDragHandler指令碼的物件。
③pointerEnter:當前Touch位置發出的射線擊中的第一個物體。
④pointerCurrentRaycast:當前位置發出射線的計算結果,包括當前擊中的物體等資訊。
1.按下
一次完整的touch行為的開始,新生成一個PointerEventData加入快取中
<1>記錄pressPosition,用於開始拖動的判定。
<2>因為當前按下的位置在A上,所以設定pointerEnter為A。且A上的指令碼繼承了IPointerEnterHandler介面,所以執行A上的PointerEnter函式。
<3>因為當前按下的位置在A上,且A上的指令碼繼承了IProinterDownHandler、IPointerDragHandler介面,所以設定pointerPress和pointerDrag為A。用於對後續抬起時的Click等事件做判定。同時執行PointerDown函式。
2.拖動
在拖動距離超過Threshold之前什麼都不做。超過後開始不停的執行pointerDrag(A)物件上的OnDrag函式。
3.離開A
在離開A時,pointerEnter物件由A變為了null,所以執行pointerEnter(A)物件上的PointerExit函式。
4.拖動
同2。
5.進入B
在進入B時,pointerEnter物件由null變為了B,所以執行pointerEnter(B)上的PointerEnter函式。
6.抬起
touch行為的結束,從快取中移除這個PointerEventData
<1>執行pointerPress(A)物件上的PointerUp函式。
<2>由於抬起時射線擊中的物件是B,而不是pointerPress(A)物件。所以不執行pointerPress(A)物件上的OnClick函式,而執行B上的OnDrop函式。
<3>執行B上的PointExit函式。
小結
1.簡單來說EventSysetm的處理過程就是迴圈獲取Touch資料。根據Touch資料來推測完整的Touch行為,來維護對應的PointerEventData,在此基礎上進行事件的計算和分發。
2.EventSystem的程式碼量比較少但特殊處理的地方還挺多的,畢竟一個完善的系統,所有情況都得考慮到位。所以閱讀程式碼時可以先看最核心的Process相關的程式碼(Touch和Mouse先選一個),像InputModule切換、BaseInput的處理、Touch的特殊情況處理這些可以先略過,把握住核心思路之後再看這些部分