相關組件和類
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的特殊情況處理這些可以先略過,把握住核心思路之后再看這些部分。