1. 用于事件执行的静态类 ExecuteEvents:
触发时执行gameObject上的对应事件处理;
namespace UnityEngine.EventSystems
{
public static class ExecuteEvents
{
//声明委托类型,各事件处理回调
public delegate void EventFunction<T1>(T1 handler, BaseEventData eventData);
//数据有效判断
public static T ValidateEventData<T>(BaseEventData data) where T : class
{
if ((data as T) == null)
throw new ArgumentException(String.Format("Invalid type: {0} passed to event expecting {1}", data.GetType(), typeof(T)));
return data as T;
}
//指针移动回调
private static readonly EventFunction<IPointerMoveHandler> s_PointerMoveHandler = Execute;
private static void Execute(IPointerMoveHandler handler, BaseEventData eventData)
{
handler.OnPointerMove(ValidateEventData<PointerEventData>(eventData));
}
//...其余各事件处理回调
//从子到父获取一系列Transform,冒泡效果
private static void GetEventChain(GameObject root, IList<Transform> eventChain)
{
eventChain.Clear();
if (root == null)
return;
var t = root.transform;
while (t != null)
{
eventChain.Add(t);
t = t.parent;
}
}
/**
* 只在一个GameObject上执行事件Handler,不冒泡
* T为某类事件Handler
*/
public static bool Execute<T>(GameObject target, BaseEventData eventData, EventFunction<T> functor) where T : IEventSystemHandler
{
var internalHandlers = ListPool<IEventSystemHandler>.Get();
//获取target上T类型的所有事件Handler,且组件是激活的
GetEventList<T>(target, internalHandlers);
// if (s_InternalHandlers.Count > 0)
// Debug.Log("Executinng " + typeof (T) + " on " + target);
var internalHandlersCount = internalHandlers.Count;
for (var i = 0; i < internalHandlersCount; i++)
{
T arg;
try
{
arg = (T)internalHandlers[i];
}
catch (Exception e)
{
var temp = internalHandlers[i];
Debug.LogException(new Exception(string.Format("Type {0} expected {1} received.", typeof(T).Name, temp.GetType().Name), e));
continue;
}
try
{
//执行事件处理,如 arg.OnPointerMove(ValidateEventData<PointerEventData>(eventData));
functor(arg, eventData);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
var handlerCount = internalHandlers.Count;
ListPool<IEventSystemHandler>.Release(internalHandlers);
return handlerCount > 0;
}
/// <summary>
/// Execute the specified event on the first game object underneath the current touch.
/// </summary>
private static readonly List<Transform> s_InternalTransformList = new List<Transform>(30);
/**
* 冒泡执行事件处理,直到有个gameObject上执行了事件Handler
*/
public static GameObject ExecuteHierarchy<T>(GameObject root, BaseEventData eventData, EventFunction<T> callbackFunction) where T : IEventSystemHandler
{
//从子到父获取一系列Transform
GetEventChain(root, s_InternalTransformList);
var internalTransformListCount = s_InternalTransformList.Count;
for (var i = 0; i < internalTransformListCount; i++)
{
var transform = s_InternalTransformList[i];
if (Execute(transform.gameObject, eventData, callbackFunction))
return transform.gameObject; //某个gameObject上执行了事件Handler,就结束
}
return null;
}
//是否为类型T的事件Handler,且组件是激活的
private static bool ShouldSendToComponent<T>(Component component) where T : IEventSystemHandler
{
var valid = component is T;
if (!valid)
return false;
var behaviour = component as Behaviour;
if (behaviour != null)
return behaviour.isActiveAndEnabled;
return true;
}
/// <summary>
/// Get the specified object's event event.
/// 获取指定gameObject上的类型T的事件handler
/// </summary>
private static void GetEventList<T>(GameObject go, IList<IEventSystemHandler> results) where T : IEventSystemHandler
{
// Debug.LogWarning("GetEventList<" + typeof(T).Name + ">");
if (results == null)
throw new ArgumentException("Results array is null", "results");
if (go == null || !go.activeInHierarchy)
return;
var components = ListPool<Component>.Get();
go.GetComponents(components);
var componentsCount = components.Count;
for (var i = 0; i < componentsCount; i++)
{
if (!ShouldSendToComponent<T>(components[i]))
continue;
// Debug.Log(string.Format("{2} found! On {0}.{1}", go, s_GetComponentsScratch[i].GetType(), typeof(T)));
results.Add(components[i] as IEventSystemHandler);
}
ListPool<Component>.Release(components);
// Debug.LogWarning("end GetEventList<" + typeof(T).Name + ">");
}
/// <summary>
/// Whether the specified game object will be able to handle the specified event.
/// 指定的gameObject是否含有类型T的事件handler
/// </summary>
public static bool CanHandleEvent<T>(GameObject go) where T : IEventSystemHandler
{
var internalHandlers = ListPool<IEventSystemHandler>.Get();
GetEventList<T>(go, internalHandlers);
var handlerCount = internalHandlers.Count;
ListPool<IEventSystemHandler>.Release(internalHandlers);
return handlerCount != 0;
}
/// <summary>
/// Bubble the specified event on the game object, figuring out which object will actually receive the event.
/// 冒泡找到第一个gameObject,其包含类型T的事件handler
/// </summary>
public static GameObject GetEventHandler<T>(GameObject root) where T : IEventSystemHandler
{
if (root == null)
return null;
Transform t = root.transform;
while (t != null)
{
if (CanHandleEvent<T>(t.gameObject))
return t.gameObject;
t = t.parent;
}
return null;
}
}
}
2. EventSystem 组件:
主要用于驱动输入模块更新和处理输入;
public class EventSystem : UIBehaviour
{
private List<BaseInputModule> m_SystemInputModules = new List<BaseInputModule>();
private BaseInputModule m_CurrentInputModule;
...
private void TickModules()
{
var systemInputModulesCount = m_SystemInputModules.Count;
for (var i = 0; i < systemInputModulesCount; i++)
{
//驱动输入模块更新
if (m_SystemInputModules[i] != null)
m_SystemInputModules[i].UpdateModule();
}
}
protected virtual void Update()
{
if (current != this)
return;
TickModules();
...
//输入模块处理输入
if (!changedModule && m_CurrentInputModule != null)
m_CurrentInputModule.Process();
}
}
3. 输入模块 StandaloneInputModule:
//输入模块更新
public override void UpdateModule()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
{
if (m_InputPointerEvent != null && m_InputPointerEvent.pointerDrag != null && m_InputPointerEvent.dragging)
{
//切后台时默认取消点击
ReleaseMouse(m_InputPointerEvent, m_InputPointerEvent.pointerCurrentRaycast.gameObject);
}
m_InputPointerEvent = null;
return;
}
m_LastMousePosition = m_MousePosition;
m_MousePosition = input.mousePosition;
}
public override void Process()
{
if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
return;
//currentSelectedGameObject Update
bool usedEvent = SendUpdateEventToSelectedObject();
// case 1004066 - touch / mouse events should be processed before navigation events in case
// they change the current selected gameobject and the submit button is a touch / mouse button.
// touch needs to take precedence because of the mouse emulation layer
if (!ProcessTouchEvents() && input.mousePresent)
ProcessMouseEvent();
if (eventSystem.sendNavigationEvents)
{
if (!usedEvent)
usedEvent |= SendMoveEventToSelectedObject();
if (!usedEvent)
SendSubmitEventToSelectedObject();
}
}
首先看触摸处理:
//处理触摸操作
private bool ProcessTouchEvents()
{
for (int i = 0; i < input.touchCount; ++i)
{
//正在进行的触摸操作
Touch touch = input.GetTouch(i);
if (touch.type == TouchType.Indirect)
continue;
bool released;
bool pressed;
//每个touch对应一个PointerEventData,多点触控时数据是独立的
var pointer = GetTouchPointerEventData(touch, out pressed, out released);
//处理按下释放操作
ProcessTouchPress(pointer, pressed, released);
if (!released)
{
ProcessMove(pointer); //处理指针进入退出
ProcessDrag(pointer); //处理拖拽
}
else
RemovePointerData(pointer); //数据没复用了,有gc
}
return input.touchCount > 0;
}
protected PointerEventData GetTouchPointerEventData(Touch input, out bool pressed, out bool released)
{
PointerEventData pointerData;
var created = GetPointerData(input.fingerId, out pointerData, true);
pointerData.Reset();
//当前帧按下
pressed = created || (input.phase == TouchPhase.Began);
//当前抬起
released = (input.phase == TouchPhase.Canceled) || (input.phase == TouchPhase.Ended);
//if (created)
//pointerData.position = input.position;
if (pressed)
pointerData.delta = Vector2.zero;
else
pointerData.delta = input.position - pointerData.position;
pointerData.position = input.position;
pointerData.button = PointerEventData.InputButton.Left;
if (input.phase == TouchPhase.Canceled)
{
//触摸取消了,没有目标
pointerData.pointerCurrentRaycast = new RaycastResult();
}
else
{
eventSystem.RaycastAll(pointerData, m_RaycastResultCache);
var raycast = FindFirstRaycast(m_RaycastResultCache);
pointerData.pointerCurrentRaycast = raycast;
m_RaycastResultCache.Clear();
}
pointerData.pressure = input.pressure;
pointerData.altitudeAngle = input.altitudeAngle;
pointerData.azimuthAngle = input.azimuthAngle;
pointerData.radius = Vector2.one * input.radius;
pointerData.radiusVariance = Vector2.one * input.radiusVariance;
return pointerData;
}
protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
{
//当前触摸所在的最上层对象
var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;
// PointerDown notification
if (pressed)
{
//处理按下
pointerEvent.eligibleForClick = true;
pointerEvent.delta = Vector2.zero;
pointerEvent.dragging = false;
pointerEvent.useDragThreshold = true;
//按下时的位置
pointerEvent.pressPosition = pointerEvent.position;
//按下时的Raycast
pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;
//判断当前选择的目标是否变了,变了取消掉选择
DeselectIfSelectionChanged(currentOverGo, pointerEvent);
//处理指针退出进入效果
if (pointerEvent.pointerEnter != currentOverGo)
{
// send a pointer enter to the touched element if it isn't the one to select...
HandlePointerExitAndEnter(pointerEvent, currentOverGo);
pointerEvent.pointerEnter = currentOverGo;
}
//超过双击间隔了,点击次数复位,后面会覆盖值,无效操作
var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
if (resetDiffTime >= doubleClickTime)
{
pointerEvent.clickCount = 0;
}
//执行pointerDownHandler
// search for the control that will receive the press
// if we can't find a press handler set the press
// handler to be what would receive a click.
var newPressed = ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.pointerDownHandler);
//获取点击处理gameObject
var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// didnt find a press handler... search for a click handler
if (newPressed == null)
newPressed = newClick;
// Debug.Log("Pressed: " + newPressed);
float time = Time.unscaledTime;
//应该是无效代码
if (newPressed == pointerEvent.lastPress) //应该用pointerEvent.pointerPress的值,还没赋值为新的
{
var diffTime = time - pointerEvent.clickTime;
if (diffTime < doubleClickTime)
++pointerEvent.clickCount;
else
pointerEvent.clickCount = 1;
pointerEvent.clickTime = time;
}
else
{
pointerEvent.clickCount = 1;
}
pointerEvent.pointerPress = newPressed;
pointerEvent.rawPointerPress = currentOverGo;
pointerEvent.pointerClick = newClick;
pointerEvent.clickTime = time;
//接受拖动的对象
// Save the drag handler as well
pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(currentOverGo);
if (pointerEvent.pointerDrag != null)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.initializePotentialDrag);
}
// PointerUp notification
if (released)
{
//处理抬起
//执行pointerUpHandler
// Debug.Log("Executing pressup on: " + pointer.pointerPress);
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
// Debug.Log("KeyCode: " + pointer.eventData.keyCode);
// see if we mouse up on the same element that we clicked on...
var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);
// PointerClick and Drop events
if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
{
//点击事件,按下和抬起时,是在同一个gameObject
ExecuteEvents.Execute(pointerEvent.pointerClick, pointerEvent, ExecuteEvents.pointerClickHandler);
}
//执行释放事件
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
{
ExecuteEvents.ExecuteHierarchy(currentOverGo, pointerEvent, ExecuteEvents.dropHandler);
}
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
pointerEvent.pointerClick = null;
//结束拖拽事件
if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.endDragHandler);
pointerEvent.dragging = false;
pointerEvent.pointerDrag = null;
//指针退出
// send exit events as we need to simulate this on touch up on touch device
ExecuteEvents.ExecuteHierarchy(pointerEvent.pointerEnter, pointerEvent, ExecuteEvents.pointerExitHandler);
pointerEvent.pointerEnter = null;
}
//没考虑多点触控吧
m_InputPointerEvent = pointerEvent;
}
protected void HandlePointerExitAndEnter(PointerEventData currentPointerData, GameObject newEnterTarget)
{
// if we have no target / pointerEnter has been deleted
// just send exit events to anything we are tracking
// then exit
if (newEnterTarget == null || currentPointerData.pointerEnter == null)
{
//指针退出
var hoveredCount = currentPointerData.hovered.Count;
for (var i = 0; i < hoveredCount; ++i)
{
currentPointerData.fullyExited = true;
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerExitHandler);
}
currentPointerData.hovered.Clear();
if (newEnterTarget == null)
{
currentPointerData.pointerEnter = null;
return;
}
}
// if we have not changed hover target
if (currentPointerData.pointerEnter == newEnterTarget && newEnterTarget)
{
//相同的目标时,指针移动
if (currentPointerData.IsPointerMoving())
{
var hoveredCount = currentPointerData.hovered.Count;
for (var i = 0; i < hoveredCount; ++i)
ExecuteEvents.Execute(currentPointerData.hovered[i], currentPointerData, ExecuteEvents.pointerMoveHandler);
}
return;
}
GameObject commonRoot = FindCommonRoot(currentPointerData.pointerEnter, newEnterTarget);
GameObject pointerParent = ((Component)newEnterTarget.GetComponentInParent<IPointerExitHandler>())?.gameObject;
//执行pointerExit,直到common root
// and we already an entered object from last time
if (currentPointerData.pointerEnter != null)
{
// send exit handler call to all elements in the chain
// until we reach the new target, or null!
// ** or when !m_SendPointerEnterToParent, stop when meeting a gameobject with an exit event handler
Transform t = currentPointerData.pointerEnter.transform;
while (t != null)
{
// if we reach the common root break out!
if (m_SendPointerHoverToParent && commonRoot != null && commonRoot.transform == t)
break;
//pointerParent表示新的IPointerExitHandler目标
// if we reach a PointerExitEvent break out!
if (!m_SendPointerHoverToParent && pointerParent == t.gameObject)
break;
currentPointerData.fullyExited = t.gameObject != commonRoot /* && currentPointerData.pointerEnter != newEnterTarget */;
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerExitHandler);
currentPointerData.hovered.Remove(t.gameObject);
if (m_SendPointerHoverToParent) t = t.parent;
// if we reach the common root break out!
if (commonRoot != null && commonRoot.transform == t)
break;
if (!m_SendPointerHoverToParent) t = t.parent;
}
}
//执行pointerEnter,直到common root
// now issue the enter call up to but not including the common root
var oldPointerEnter = currentPointerData.pointerEnter;
currentPointerData.pointerEnter = newEnterTarget;
if (newEnterTarget != null)
{
Transform t = newEnterTarget.transform;
while (t != null)
{
currentPointerData.reentered = t.gameObject == commonRoot && t.gameObject != oldPointerEnter;
// if we are sending the event to parent, they are already in hover mode at that point. No need to bubble up the event.
if (m_SendPointerHoverToParent && currentPointerData.reentered)
break;
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerEnterHandler);
ExecuteEvents.Execute(t.gameObject, currentPointerData, ExecuteEvents.pointerMoveHandler);
currentPointerData.hovered.Add(t.gameObject);
// stop when encountering an object with the pointerEnterHandler
if (!m_SendPointerHoverToParent && t.gameObject.GetComponent<IPointerEnterHandler>() != null)
break;
if (m_SendPointerHoverToParent) t = t.parent;
// if we reach the common root break out!
if (commonRoot != null && commonRoot.transform == t)
break;
if (!m_SendPointerHoverToParent) t = t.parent;
}
}
}
/// <summary>
/// Process movement for the current frame with the given pointer event.
/// </summary>
protected virtual void ProcessMove(PointerEventData pointerEvent)
{
var targetGO = (Cursor.lockState == CursorLockMode.Locked ? null : pointerEvent.pointerCurrentRaycast.gameObject);
HandlePointerExitAndEnter(pointerEvent, targetGO);
}
protected virtual void ProcessDrag(PointerEventData pointerEvent)
{
if (!pointerEvent.IsPointerMoving() ||
Cursor.lockState == CursorLockMode.Locked ||
pointerEvent.pointerDrag == null)
return;
//开始drag
if (!pointerEvent.dragging
&& ShouldStartDrag(pointerEvent.pressPosition, pointerEvent.position, eventSystem.pixelDragThreshold, pointerEvent.useDragThreshold))
{
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.beginDragHandler);
pointerEvent.dragging = true;
}
// Drag notification
if (pointerEvent.dragging)
{
// Before doing drag we should cancel any pointer down state
// And clear selection!
if (pointerEvent.pointerPress != pointerEvent.pointerDrag)
{
ExecuteEvents.Execute(pointerEvent.pointerPress, pointerEvent, ExecuteEvents.pointerUpHandler);
pointerEvent.eligibleForClick = false;
pointerEvent.pointerPress = null;
pointerEvent.rawPointerPress = null;
}
ExecuteEvents.Execute(pointerEvent.pointerDrag, pointerEvent, ExecuteEvents.dragHandler);
}
}
再看鼠标处理(和触摸处理相似):
/// <summary>
/// Process all mouse events.
/// </summary>
protected void ProcessMouseEvent(int id)
{
var mouseData = GetMousePointerEventData(id);
var leftButtonData = mouseData.GetButtonState(PointerEventData.InputButton.Left).eventData;
m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
// Process the first mouse button fully
ProcessMousePress(leftButtonData);
ProcessMove(leftButtonData.buttonData);
ProcessDrag(leftButtonData.buttonData);
// Now process right / middle clicks
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData);
ProcessMousePress(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData);
ProcessDrag(mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData);
//滚轮处理
if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
{
var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(leftButtonData.buttonData.pointerCurrentRaycast.gameObject);
ExecuteEvents.ExecuteHierarchy(scrollHandler, leftButtonData.buttonData, ExecuteEvents.scrollHandler);
}
}
/// <summary>
/// Return the current MouseState.
/// </summary>
protected virtual MouseState GetMousePointerEventData(int id)
{
// Populate the left button...
PointerEventData leftData;
var created = GetPointerData(kMouseLeftId, out leftData, true);
leftData.Reset();
if (created)
leftData.position = input.mousePosition;
Vector2 pos = input.mousePosition;
if (Cursor.lockState == CursorLockMode.Locked)
{
// We don't want to do ANY cursor-based interaction when the mouse is locked
leftData.position = new Vector2(-1.0f, -1.0f);
leftData.delta = Vector2.zero;
}
else
{
leftData.delta = pos - leftData.position;
leftData.position = pos;
}
leftData.scrollDelta = input.mouseScrollDelta;
leftData.button = PointerEventData.InputButton.Left;
eventSystem.RaycastAll(leftData, m_RaycastResultCache);
var raycast = FindFirstRaycast(m_RaycastResultCache);
leftData.pointerCurrentRaycast = raycast;
m_RaycastResultCache.Clear();
//复制鼠标位移数据到其他按钮
// copy the apropriate data into right and middle slots
PointerEventData rightData;
GetPointerData(kMouseRightId, out rightData, true);
rightData.Reset();
CopyFromTo(leftData, rightData);
rightData.button = PointerEventData.InputButton.Right;
PointerEventData middleData;
GetPointerData(kMouseMiddleId, out middleData, true);
middleData.Reset();
CopyFromTo(leftData, middleData);
middleData.button = PointerEventData.InputButton.Middle;
m_MouseState.SetButtonState(PointerEventData.InputButton.Left, StateForMouseButton(0), leftData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Right, StateForMouseButton(1), rightData);
m_MouseState.SetButtonState(PointerEventData.InputButton.Middle, StateForMouseButton(2), middleData);
return m_MouseState;
}
4. 射线检测:
在判断当前输入所在的游戏对象时,用到了EventSystem组件的射线检测;
/// <summary>
/// Raycast into the scene using all configured BaseRaycasters.
/// </summary>
/// <param name="eventData">Current pointer data.</param>
/// <param name="raycastResults">List of 'hits' to populate.</param>
public void RaycastAll(PointerEventData eventData, List<RaycastResult> raycastResults)
{
raycastResults.Clear();
var modules = RaycasterManager.GetRaycasters();
var modulesCount = modules.Count;
for (int i = 0; i < modulesCount; ++i)
{
var module = modules[i];
if (module == null || !module.IsActive())
continue;
module.Raycast(eventData, raycastResults);
}
//获取最上层的目标,从大到小排序
raycastResults.Sort(s_RaycastComparer);
}
我们看GraphicRaycaster:
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
if (canvas == null)
return;
//获取当前canvas的所有Raycastable Graphics
var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);
if (canvasGraphics == null || canvasGraphics.Count == 0)
return;
//获取displayIndex
int displayIndex;
var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
displayIndex = canvas.targetDisplay;
else
displayIndex = currentEventCamera.targetDisplay;
//z分量为displayIndex
Vector3 eventPosition = MultipleDisplayUtilities.GetRelativeMousePositionForRaycast(eventData);
// Discard events that are not part of this display so the user does not interact with multiple displays at once.
if ((int) eventPosition.z != displayIndex)
return;
// Convert to view space,视角空间归一化
Vector2 pos;
if (currentEventCamera == null)
{
// Multiple display support only when not the main display. For display 0 the reported
// resolution is always the desktops resolution since its part of the display API,
// so we use the standard none multiple display method. (case 741751)
float w = Screen.width;
float h = Screen.height;
if (displayIndex > 0 && displayIndex < Display.displays.Length)
{
w = Display.displays[displayIndex].systemWidth;
h = Display.displays[displayIndex].systemHeight;
}
pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
}
else
pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
// If it's outside the camera's viewport, do nothing
if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
return;
//阻塞距离
float hitDistance = float.MaxValue;
Ray ray = new Ray();
//获取射线
if (currentEventCamera != null)
ray = currentEventCamera.ScreenPointToRay(eventPosition);
//计算物理阻塞hitDistance
...
m_RaycastResults.Clear();
//获取当前eventPosition下的Graphics
Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
int totalCount = m_RaycastResults.Count;
for (var index = 0; index < totalCount; index++)
{
var go = m_RaycastResults[index].gameObject;
bool appendGraphic = true;
if (ignoreReversedGraphics)
{
if (currentEventCamera == null)
{
// If we dont have a camera we know that we should always be facing forward
var dir = go.transform.rotation * Vector3.forward;
appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
}
else
{
// If we have a camera compare the direction against the cameras forward.
var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
//不直接用cameraForward算,否则在currentEventCamera.nearClipPlane之前也有效
appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
}
}
if (appendGraphic)
{
float distance = 0;
Transform trans = go.transform;
Vector3 transForward = trans.forward;
if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
distance = 0;
else
{
// http://geomalgorithms.com/a06-_intersect-2.html
//投影值除以余弦值得到斜边长度
distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
// Check to see if the go is behind the camera.
if (distance < 0)
continue;
}
if (distance >= hitDistance)
continue;
var castResult = new RaycastResult
{
gameObject = go,
module = this,
distance = distance,
screenPosition = eventPosition,
displayIndex = displayIndex,
index = resultAppendList.Count,
depth = m_RaycastResults[index].depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
worldPosition = ray.origin + ray.direction * distance,
worldNormal = -transForward
};
resultAppendList.Add(castResult);
}
}
}
/// <summary>
/// Perform a raycast into the screen and collect all graphics underneath it.
/// </summary>
[NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
{
// Necessary for the event system
int totalCount = foundGraphics.Count;
for (int i = 0; i < totalCount; ++i)
{
Graphic graphic = foundGraphics[i];
// -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
continue;
//ui组件矩形是否包含当前点击点
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
continue;
//超出渲染区域
if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
continue;
if (graphic.Raycast(pointerPosition, eventCamera))
{
s_SortedGraphics.Add(graphic);
}
}
s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
totalCount = s_SortedGraphics.Count;
for (int i = 0; i < totalCount; ++i)
results.Add(s_SortedGraphics[i]);
s_SortedGraphics.Clear();
}