Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs

194 lines
6.2 KiB
C#

#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace io.github.hatayama.uLoopMCP
{
// Shared UI raycast logic used by SimulateMouseUiTool and InputReplayer.
internal static class UiRaycastHelper
{
public static RaycastResult? RaycastUI(Vector2 screenPosition, EventSystem eventSystem)
{
PointerEventData pointerData = new PointerEventData(eventSystem)
{
position = screenPosition
};
List<RaycastResult> results = new List<RaycastResult>();
eventSystem.RaycastAll(pointerData, results);
RaycastResult? canvasSpaceHit = RaycastCanvasSpace(screenPosition);
if (results.Count > 0)
{
RaycastResult firstHit = results[0];
if (canvasSpaceHit != null && ShouldPreferCanvasSpaceHit(canvasSpaceHit.Value, firstHit))
{
return canvasSpaceHit;
}
return firstHit;
}
// EventSystem clips at Screen.width/height, which can be smaller than the
// Canvas layout space (Game view target resolution). Fall back to manual hit testing.
return canvasSpaceHit;
}
// Bypass EventSystem's Screen-bounds clipping by directly testing Graphic rects in Canvas space.
// Only supports ScreenSpaceOverlay canvases where world positions equal Canvas-space positions.
public static RaycastResult? RaycastCanvasSpace(Vector2 canvasPosition)
{
Canvas[] canvases = Object.FindObjectsByType<Canvas>(FindObjectsSortMode.None);
RaycastResult? bestHit = null;
foreach (Canvas canvas in canvases)
{
if (!canvas.gameObject.activeInHierarchy)
{
continue;
}
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay)
{
continue;
}
GraphicRaycaster? raycaster = canvas.GetComponent<GraphicRaycaster>();
if (raycaster == null || !raycaster.enabled)
{
continue;
}
Graphic[] graphics = canvas.GetComponentsInChildren<Graphic>();
foreach (Graphic graphic in graphics)
{
if (!IsRaycastCandidate(graphic, canvasPosition))
{
continue;
}
RaycastResult candidate = new RaycastResult
{
gameObject = graphic.gameObject,
module = raycaster,
screenPosition = canvasPosition,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
depth = graphic.depth
};
if (bestHit == null || CompareRaycastPriority(candidate, bestHit.Value) > 0)
{
bestHit = candidate;
}
}
}
return bestHit;
}
// Respects CanvasGroup.blocksRaycasts, Mask, RectMask2D, and custom ICanvasRaycastFilter
private static bool IsRaycastCandidate(Graphic graphic, Vector2 canvasPosition)
{
if (!graphic.gameObject.activeInHierarchy || !graphic.enabled)
{
return false;
}
if (!graphic.raycastTarget || graphic.depth == -1 || graphic.canvasRenderer.cull)
{
return false;
}
if (!RectTransformUtility.RectangleContainsScreenPoint(
graphic.rectTransform, canvasPosition, null))
{
return false;
}
return graphic.Raycast(canvasPosition, null);
}
private static bool ShouldPreferCanvasSpaceHit(RaycastResult canvasSpaceHit, RaycastResult eventSystemHit)
{
if (!IsGraphicRaycast(canvasSpaceHit))
{
return false;
}
if (!IsGraphicRaycast(eventSystemHit))
{
return true;
}
return CompareRaycastPriority(canvasSpaceHit, eventSystemHit) > 0;
}
private static bool IsGraphicRaycast(RaycastResult raycastResult)
{
return raycastResult.module is GraphicRaycaster;
}
private static int CompareRaycastPriority(RaycastResult left, RaycastResult right)
{
int sortOrderPriority = Compare(GetSortOrderPriority(left), GetSortOrderPriority(right));
if (sortOrderPriority != 0)
{
return sortOrderPriority;
}
int renderOrderPriority = Compare(GetRenderOrderPriority(left), GetRenderOrderPriority(right));
if (renderOrderPriority != 0)
{
return renderOrderPriority;
}
int sortingLayer = Compare(GetSortingLayerValue(left), GetSortingLayerValue(right));
if (sortingLayer != 0)
{
return sortingLayer;
}
int sortingOrder = Compare(left.sortingOrder, right.sortingOrder);
if (sortingOrder != 0)
{
return sortingOrder;
}
return Compare(left.depth, right.depth);
}
private static int GetSortOrderPriority(RaycastResult result)
{
return result.module != null ? result.module.sortOrderPriority : 0;
}
private static int GetRenderOrderPriority(RaycastResult result)
{
return result.module != null ? result.module.renderOrderPriority : 0;
}
private static int GetSortingLayerValue(RaycastResult result)
{
return SortingLayer.GetLayerValueFromID(result.sortingLayer);
}
private static int Compare(int left, int right)
{
if (left > right)
{
return 1;
}
if (left < right)
{
return -1;
}
return 0;
}
}
}