Skip to content

fix: simulate-mouse-ui clicks visible overlay UI instead of hidden targets at scaled Game view resolutions - #1324

Closed
hatayama wants to merge 4 commits into
mainfrom
feature/hatayama/prefer-overlay-ui-raycast-fallback
Closed

fix: simulate-mouse-ui clicks visible overlay UI instead of hidden targets at scaled Game view resolutions#1324
hatayama wants to merge 4 commits into
mainfrom
feature/hatayama/prefer-overlay-ui-raycast-fallback

Conversation

@hatayama

@hatayama hatayama commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • simulate-mouse-ui (and input replay) now dispatches to the visible ScreenSpaceOverlay UI element instead of a target behind it when the Game view runs at a scaled target resolution.

User Impact

  • Before: when the Game view target resolution was larger than the actual window, EventSystem clipped UI raycasts at Screen.width/Screen.height. If a non-UI raycaster (e.g. PhysicsRaycaster) still hit at that position, the click went to that hidden target — or reported the wrong element — even though a real user would reach the frontmost button.
  • After: the click lands on the frontmost overlay UI element, matching what the user sees. Drag target resolution behaves the same way.

Changes

  • UiRaycastHelper.RaycastUI: when the first EventSystem result does not come from a GraphicRaycaster, prefer the existing Canvas-space hit test (ScreenSpaceOverlay UI always renders in front of world-space content). UI-vs-UI ordering stays delegated to Unity's own RaycastComparer.
  • SimulateMouseUiTool.UpdatePointerRaycast: unified the drag path's duplicated raycast-plus-fallback logic onto the shared helper, so DragMove resolves targets the same way.
  • Added a PlayMode regression test that reproduces the clipped-UI case with a test-only BaseRaycaster, since this project intentionally disables the physics modules.

Relationship to #1318

Alternative approach to #1318 (thanks @prog-k-dev for the report and investigation). On Unity 2022.3.62f3 and 6000.3, EventSystem.RaycastAll already orders overlay UI ahead of physics hits whenever the GraphicRaycaster actually hits the element (raycaster sortOrderPriority resolves first), so this PR keeps Unity's ordering for UI-vs-UI and only covers the case where the UI hit is clipped away entirely.

Closes #1317.

Verification

  • TDD: the new test fails on main ("Overlay UI should receive the click") and passes with this change.
  • uloop compile: 0 errors / 0 warnings.
  • uloop run-tests --test-mode PlayMode for Tests.PlayMode.SimulateMouseUiTests: 22/22 passed.
  • Full PlayMode assembly: 60/61 passed; the single failure (SimulateKeyboardTests.Press_Should_CreateOverlayCanvas_WithUnitScale) also fails on a clean main checkout in this environment (Game view scale dependent) and is unrelated.

Review in cubic

Overview

This PR fixes simulate-mouse-ui so simulated clicks at scaled Game view resolutions (where Screen bounds can clip EventSystem raycasts) target the visually frontmost ScreenSpaceOverlay uGUI element instead of selecting behind-the-overlay or non-UI raycast targets.

What changed

UiRaycastHelper.RaycastUI

  • Computes a Canvas-space hit via RaycastCanvasSpace.
  • When EventSystem.RaycastAll returns results, it no longer blindly uses results[0]; instead it conditionally prefers the Canvas-space hit over results[0] via a new ShouldPreferCanvasSpaceHit helper.
  • When EventSystem.RaycastAll returns no results, it falls back to the Canvas-space hit.

UiRaycastHelper.RaycastCanvasSpace

  • Updated Canvas-space hit selection to build candidate RaycastResults that include:
    • sortingLayer
    • sortingOrder
    • depth
  • Selects the “best” candidate using CompareRaycastPriority, incorporating:
    • module sort/render priorities
    • sorting layer value
    • sorting order
    • depth
  • Returned RaycastResult is populated with the chosen graphic and sortingOrder.

SimulateMouseUiTool.UpdatePointerRaycast

  • Simplified pointer raycast resolution to directly set pointerData.pointerCurrentRaycast from UiRaycastHelper.RaycastUI(position, EventSystem.current), defaulting to an empty RaycastResult when null.

Tests (PlayMode regression coverage)

Updated Assets/Tests/PlayMode/SimulateMouseUiTests.cs with new PlayMode regression tests covering:

  • Prefer clipped ScreenSpaceOverlay over a non-UI raycast hit (using test-only BaseRaycaster/RaycastResult injection).
  • Prefer overlay UI over a Physics2DRaycaster hit when the physics raycaster is made higher-priority.
  • Prefer a clipped higher-order overlay UI over a lower-priority GraphicRaycaster hit.

The test file also adds helper raycasters:

  • AlwaysHitRaycaster : BaseRaycaster
  • AlwaysHitGraphicRaycaster : GraphicRaycaster
  • HighPriorityPhysics2DRaycaster : Physics2DRaycaster

Dependencies

  • Added com.unity.modules.physics2d to Packages/manifest.json and lockfile to ensure PlayMode tests can use Physics2DRaycaster.

Verification

  • New regression tests fail on main and pass with this change.
  • PlayMode SimulateMouseUiTests: 22/22 passed.
  • Full PlayMode assembly: 60/61 passed (one unrelated existing failure on main).

Class Diagram

┌─────────────────────────────────────────────┐
│             UiRaycastHelper (static)      │
├─────────────────────────────────────────────┤
│ + RaycastUI(Vector2, EventSystem)         │
│   - Prefer Canvas-space hit conditionally │
│ + RaycastCanvasSpace(Vector2)           │
│   - Candidate ranking by priority         │
└─────────────────────────────────────────────┘
            △ used by
┌─────────────────────────────────────────────┐
│             SimulateMouseUiTool           │
├─────────────────────────────────────────────┤
│ - UpdatePointerRaycast(...)              │
│   sets pointerCurrentRaycast from helper │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│           InputReplayer (indirect)       │
├─────────────────────────────────────────────┤
│ - Dispatch/click handling uses helper     │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│           Test-only Raycaster Helpers    │
├─────────────────────────────────────────────┤
│ AlwaysHitRaycaster : BaseRaycaster       │
│ AlwaysHitGraphicRaycaster : GraphicRaycaster
│ HighPriorityPhysics2DRaycaster          : Physics2DRaycaster
└─────────────────────────────────────────────┘

EventSystem clips GraphicRaycaster hits at Screen.width/height, which can be
smaller than the Canvas layout space (scaled Game view resolution). When a
non-UI raycaster (e.g. PhysicsRaycaster) still hit at such a position,
RaycastUI dispatched to that first result even though the ScreenSpaceOverlay
UI element in front was the target a real user would reach. Prefer the
existing Canvas-space hit test whenever the first EventSystem result does not
come from a GraphicRaycaster; UI-vs-UI ordering stays delegated to Unity's
own RaycastComparer.

- Unify the drag path's duplicated raycast-plus-fallback logic onto
  UiRaycastHelper.RaycastUI so DragMove resolves targets the same way
- Add a PlayMode regression test that reproduces the clipped-UI case with a
  test-only BaseRaycaster, since this project disables the physics modules
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a535c09d-a0c7-43a9-bacd-994f977bbb11

📥 Commits

Reviewing files that changed from the base of the PR and between 704b227 and 6d701aa.

📒 Files selected for processing (4)
  • Assets/Tests/PlayMode/SimulateMouseUiTests.cs
  • Packages/manifest.json
  • Packages/packages-lock.json
  • Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs

📝 Walkthrough

Walkthrough

SimulateMouseUi now selects raycast targets using a priority-aware comparison of Canvas-space UI candidates against EventSystem results, ordering by module sort/render priorities, then sorting layer, order, and depth. The tool delegates raycast selection to an enhanced UiRaycastHelper, and Physics2D dependency support is added. Three new PlayMode tests validate that clipped overlay UI, Physics2D conflicts, and layered modal scenarios all dispatch clicks to the frontmost interactive UI element rather than lower-priority raycast targets.

Changes

UI Raycast Priority Selection

Layer / File(s) Summary
Raycast priority comparison and Canvas-space preference logic in UiRaycastHelper
Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs
RaycastUI precomputes a Canvas-space candidate and conditionally prefers it over EventSystem.RaycastAll results using a new ShouldPreferCanvasSpaceHit helper. RaycastCanvasSpace now builds RaycastResult candidates including sorting layer, order, and depth, then selects the best using CompareRaycastPriority, which orders by module sort/render priorities, then sorting-layer value, then sorting order, then graphic depth.
SimulateMouseUiTool pointer raycast update
Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs
UpdatePointerRaycast now delegates directly to UiRaycastHelper.RaycastUI with a null-to-empty RaycastResult fallback, removing prior manual RaycastAll selection and explicit Canvas-space fallback logic. File imports updated to add System.Threading and remove unused namespaces.
PlayMode test coverage for raycast priority scenarios and Physics2D dependency
Packages/manifest.json, Packages/packages-lock.json, Assets/Tests/PlayMode/SimulateMouseUiTests.cs
Physics2D module dependency (com.unity.modules.physics2d@1.0.0) added to support Physics2D raycaster tests. Three new PlayMode tests validate: offscreen overlay UI receiving clicks over non-UI raycasters, ScreenSpace-Overlay UI prioritized over Physics2D hits, and higher-order overlay UI preferred over lower-priority GraphicRaycaster hits. Test helpers include AlwaysHitRaycaster, AlwaysHitGraphicRaycaster, HighPriorityPhysics2DRaycaster, and GetWorldPointOnPhysicsPlane utility.

Sequence Diagram(s)

sequenceDiagram
  participant SimTool as SimulateMouseUiTool
  participant UiHelper as UiRaycastHelper
  participant EventSys as EventSystem
  participant Canvas as RaycastCanvasSpace
  participant CompPrio as CompareRaycastPriority
  SimTool->>UiHelper: RaycastUI(screenPosition, EventSystem.current)
  UiHelper->>EventSys: RaycastAll(screenPosition, results)
  UiHelper->>Canvas: RaycastCanvasSpace(screenPosition)
  Canvas->>Canvas: Build RaycastResult candidates
  Canvas->>CompPrio: Compare by module priority, layer, order, depth
  CompPrio-->>Canvas: Best candidate
  alt EventSystem results present
    UiHelper->>CompPrio: ShouldPreferCanvasSpaceHit
    CompPrio-->>UiHelper: Preferred result
  else No EventSystem results
    UiHelper-->>UiHelper: Use Canvas-space candidate
  end
  UiHelper-->>SimTool: RaycastResult
  SimTool->>SimTool: pointerData.pointerCurrentRaycast = result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR partially addresses #1317 objectives: it fixes Physics2D-vs-UI cases but per PR comments, does not fully resolve UI-vs-UI ordering when both are GraphicRaycasters with different priorities. Verify whether the UI-vs-UI ordering limitation (clipped overlay vs lower-priority full-screen mask) is acceptable for this PR or requires additional work beyond the current Canvas-space fallback approach.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: simulate-mouse-ui now clicks visible overlay UI instead of hidden targets at scaled Game view resolutions, which aligns with the primary change.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: UI raycast priority comparison, test helpers for regression coverage, Physics2D dependency addition, and unified raycast logic in SimulateMouseUiTool.
Description check ✅ Passed PR description thoroughly documents the fix rationale, implementation approach, test strategy, and known limitations regarding UI-vs-UI ordering edge cases.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hatayama/prefer-overlay-ui-raycast-fallback

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Assets/Tests/PlayMode/SimulateMouseUiTests.cs`:
- Around line 143-148: The test currently only asserts the first RaycastResult
isn't from a GraphicRaycaster but doesn't ensure the clipped overlay isn't
present later; update the assertions after calling
EventSystem.current.RaycastAll(pointerData, raycastResults) to validate that no
entry in raycastResults has module is GraphicRaycaster (or specifically that the
overlay GameObject/target is not contained in raycastResults), e.g., iterate
raycastResults and Assert.IsFalse for any r.module is GraphicRaycaster (or
r.gameObject == overlayTarget) so the overlay hit is proven to be fully
excluded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ec39e33a-7037-4498-afe4-2a9cb913aa48

📥 Commits

Reviewing files that changed from the base of the PR and between ba81baf and a27fe0d.

📒 Files selected for processing (3)
  • Assets/Tests/PlayMode/SimulateMouseUiTests.cs
  • Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs
  • Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs

Comment thread Assets/Tests/PlayMode/SimulateMouseUiTests.cs
Comment thread Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs Outdated
Comment thread Assets/Tests/PlayMode/SimulateMouseUiTests.cs
Rank canvas-space fallback candidates by sorting layer before sorting
order, since sortingOrder only orders canvases within the same sorting
layer. Also assert in the regression test that the overlay element is
fully clipped out of EventSystem results, so the test provably exercises
the canvas-space fallback path instead of relying on RaycastComparer
internals to keep UI hits in first place.
@prog-k-dev

Copy link
Copy Markdown
Contributor

Thanks for putting this alternative fix together. I tested this branch with a minimal saved-scene repro project:

https://github.com/prog-k-dev/uloop-raycast-repro

Full verification details are in #1317:

#1317 (comment)

Short version: this branch still fails in the UI-vs-UI ordering case where the first EventSystem.RaycastAll result is a lower-priority GraphicRaycaster hit from a full-screen mask, while the front ScreenSpaceOverlay button is hittable by its own Graphic but absent from EventSystem.RaycastAll. The same saved scene and coordinate are handled correctly by #1318 (b23479b1395b730a779988430b591187e28ddf69).

hatayama added 2 commits June 14, 2026 13:33
Compare Canvas-space overlay candidates against EventSystem hits using UI raycast priority so clipped frontmost overlay controls can still win over lower-priority GraphicRaycaster results.
Cover the real Physics2D raycaster path in the test Unity project while keeping the package manifest free of Physics2D dependencies.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs">

<violation number="1" location="Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs:26">
P1: Manual Canvas-space fallback now overrides existing UI hits, which can reorder UI-vs-UI selection versus EventSystem. Restrict fallback preference to cases where EventSystem first hit is non-UI.

(Based on your team's feedback about limiting Canvas-space fallback scope to non-UI/no-usable-UI cases.) [FEEDBACK_USED].</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

return results[0];
RaycastResult firstHit = results[0];

if (canvasSpaceHit != null && ShouldPreferCanvasSpaceHit(canvasSpaceHit.Value, firstHit))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Manual Canvas-space fallback now overrides existing UI hits, which can reorder UI-vs-UI selection versus EventSystem. Restrict fallback preference to cases where EventSystem first hit is non-UI.

(Based on your team's feedback about limiting Canvas-space fallback scope to non-UI/no-usable-UI cases.) .

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/src/Editor/Api/McpTools/SimulateMouseUi/UiRaycastHelper.cs, line 26:

<comment>Manual Canvas-space fallback now overrides existing UI hits, which can reorder UI-vs-UI selection versus EventSystem. Restrict fallback preference to cases where EventSystem first hit is non-UI.

(Based on your team's feedback about limiting Canvas-space fallback scope to non-UI/no-usable-UI cases.) .</comment>

<file context>
@@ -17,38 +17,31 @@ internal static class UiRaycastHelper
                 RaycastResult firstHit = results[0];
 
-                if (firstHit.module is GraphicRaycaster)
+                if (canvasSpaceHit != null && ShouldPreferCanvasSpaceHit(canvasSpaceHit.Value, firstHit))
                 {
-                    return firstHit;
</file context>
Suggested change
if (canvasSpaceHit != null && ShouldPreferCanvasSpaceHit(canvasSpaceHit.Value, firstHit))
if (canvasSpaceHit != null && !IsGraphicRaycast(firstHit))

@hatayama
hatayama marked this pull request as draft June 14, 2026 06:02
@hatayama hatayama closed this Jun 14, 2026
@hatayama
hatayama deleted the feature/hatayama/prefer-overlay-ui-raycast-fallback branch July 10, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

simulate-mouse-ui can select lower-priority raycast targets instead of frontmost UI

2 participants