diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md index 6ba18c90f3..40891bc388 100644 --- a/.agents/skills/uloop-clear-console/SKILL.md +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-clear-console -description: "Clear all Unity Console log entries. Use when you need to: (1) Clear console before running tests or compilation, (2) Start a fresh debugging session, (3) Remove noisy logs to isolate specific output." +description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- # uloop clear-console diff --git a/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md index f13d06abd1..0f6846d80d 100644 --- a/.agents/skills/uloop-compile/SKILL.md +++ b/.agents/skills/uloop-compile/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-compile -description: "Compile Unity project and report errors/warnings. Use when you need to: (1) Verify code compiles after C# file edits, (2) Check for compile errors before testing, (3) Force full recompilation with Domain Reload. Returns error and warning counts." +description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- # uloop compile @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md index 220eeebe91..5f830696ec 100644 --- a/.agents/skills/uloop-control-play-mode/SKILL.md +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-control-play-mode -description: "Control Unity Editor play mode (play/stop/pause). Use when you need to: (1) Start play mode to test game behavior, (2) Stop play mode to return to edit mode, (3) Pause play mode for frame-by-frame inspection." +description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- # uloop control-play-mode diff --git a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md index de0ed73fc1..31ddf7adc1 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -184,6 +184,8 @@ return $"Asset has {dependencies.Length} dependencies"; ## Refresh AssetDatabase +Use this after terminal-based asset file changes when Unity needs to import them and generate `.meta` files. + ```csharp using UnityEditor; diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index e14ceca3d9..15c34ac072 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-components true +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index d82b7c6149..b1d94797db 100644 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -uloop get-hierarchy --root-path "Canvas" --max-depth 3 --include-components true +uloop get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.agents/skills/uloop-find-game-objects/SKILL.md b/.agents/skills/uloop-find-game-objects/SKILL.md index 9c809e658c..d7084b4a91 100644 --- a/.agents/skills/uloop-find-game-objects/SKILL.md +++ b/.agents/skills/uloop-find-game-objects/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-find-game-objects -description: "Use first when the user asks about the currently selected GameObject in the Unity Hierarchy. Inspect selected object details with `--search-mode Selected` before using `execute-dynamic-code`. Use when you need to: (1) Get details and component properties for selected GameObject(s), (2) Search for objects by name, regex, or path, (3) Find objects with specific components, tags, or layers. Use get-hierarchy when the child tree under the selection is needed. Returns hierarchy paths, active state, tags, layers, and components (or writes to a file when multiple GameObjects are selected)." +description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- # uloop find-game-objects diff --git a/.agents/skills/uloop-focus-window/SKILL.md b/.agents/skills/uloop-focus-window/SKILL.md index 87dbc35876..9379ef080b 100644 --- a/.agents/skills/uloop-focus-window/SKILL.md +++ b/.agents/skills/uloop-focus-window/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-focus-window -description: "Bring Unity Editor window to front via uloop CLI. Use when you need to: (1) Focus Unity Editor before capturing screenshots, (2) Ensure Unity window is visible for visual checks, (3) Bring Unity to foreground for user interaction." +description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- # uloop focus-window diff --git a/.agents/skills/uloop-get-hierarchy/SKILL.md b/.agents/skills/uloop-get-hierarchy/SKILL.md index 976e0de5a6..8a5a932952 100644 --- a/.agents/skills/uloop-get-hierarchy/SKILL.md +++ b/.agents/skills/uloop-get-hierarchy/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-get-hierarchy -description: "Get Unity scene hierarchy as a structured tree from all roots, a root path, or the current Hierarchy selection. Use this when you need the child tree, parent-child structure, or descendants under selected GameObject(s) with `--use-selection`. Use find-game-objects for selected object details and component properties. Hierarchy data is written to a JSON file on disk and the response returns the file path (not the tree inline) — open the file to read the structure." +description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- # uloop get-hierarchy diff --git a/.agents/skills/uloop-get-logs/SKILL.md b/.agents/skills/uloop-get-logs/SKILL.md index 1ca03fc116..7cb5f5f311 100644 --- a/.agents/skills/uloop-get-logs/SKILL.md +++ b/.agents/skills/uloop-get-logs/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-get-logs -description: "Retrieve current Unity Console entries via uloop CLI. Use when you need to: (1) inspect errors, warnings, or logs after compile, tests, PlayMode, or dynamic code execution, (2) search current Console messages or stack traces, (3) confirm whether a recent Unity operation emitted logs. Prefer this over reading Editor.log or Unity log files for normal Console contents; use log files only for startup, crash, freeze, or uloop connection failures." +description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- # uloop get-logs diff --git a/.agents/skills/uloop-launch/SKILL.md b/.agents/skills/uloop-launch/SKILL.md index 411f481ba0..bedf8464ed 100644 --- a/.agents/skills/uloop-launch/SKILL.md +++ b/.agents/skills/uloop-launch/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-launch -description: "Launch Unity project with matching Editor version via uloop CLI. Use when you need to: (1) Open a Unity project with the correct Editor version, (2) Restart Unity to apply changes, (3) Switch build target when launching." +description: "Use when Unity Editor is not running or needs a clean restart." --- # uloop launch diff --git a/.agents/skills/uloop-raycast/SKILL.md b/.agents/skills/uloop-raycast/SKILL.md new file mode 100644 index 0000000000..b464ae09c3 --- /dev/null +++ b/.agents/skills/uloop-raycast/SKILL.md @@ -0,0 +1,61 @@ +--- +name: uloop-raycast +description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." +--- + +# uloop raycast + +Check what a top-left Game View coordinate hits in 3D physics. + +## Usage + +```bash +uloop raycast --x --y [--layer-mask ] [--max-distance ] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--layer-mask` | number | Unity default raycast layers | Physics layer mask used by the raycast. | +| `--max-distance` | number | `1000` | Maximum raycast distance in world units. | + +## Coordinate System + +- `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +## Examples + +```bash +# Check what is under a screenshot coordinate +uloop raycast --x 960 --y 540 + +# Check only specific layers +uloop raycast --x 960 --y 540 --layer-mask 1 +``` + +## Output + +Returns JSON with: +- `Success`: Whether the command completed +- `Message`: Status message +- `Hit`: Whether physics hit anything +- `HitGameObjectName` / `HitGameObjectPath`: Hit object identity when `Hit` is true +- `HitLayer` / `HitLayerName`: Hit object layer when `Hit` is true +- `Distance`, `HitPointX/Y/Z`, `HitNormalX/Y/Z`: Hit details when `Hit` is true +- `InputCoordinateSystem`, `UnityCoordinateSystem`, `GameViewWidth/Height`, `InputPositionX/Y`, `InjectedUnityPositionX/Y`, `CoordinateConversionFormula`: Coordinate conversion details + +## Notes + +- Requires an active `Camera.main`. +- Uses Unity Physics raycasts, not UI EventSystem raycasts. diff --git a/.agents/skills/uloop-record-input/SKILL.md b/.agents/skills/uloop-record-input/SKILL.md index 9e00ebcfcb..bfb60f7721 100644 --- a/.agents/skills/uloop-record-input/SKILL.md +++ b/.agents/skills/uloop-record-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-record-input -description: "Record keyboard and mouse input during PlayMode into a JSON file. Use when you need to: (1) Capture human gameplay input for later replay, (2) Record input sequences for E2E testing, (3) Save input for bug reproduction. Captures Input System device-state diffs frame-by-frame in PlayMode and serializes them to JSON when stopped. Requires PlayMode and the New Input System." +description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- # uloop record-input @@ -39,7 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) -- Active Input Handling must be set to `Input System Package (New)` or `Both` in Player Settings +- Use this only when the project already uses the New Input System. ## Output diff --git a/.agents/skills/uloop-replay-input/SKILL.md b/.agents/skills/uloop-replay-input/SKILL.md index 1c9cb00081..e4a103fc20 100644 --- a/.agents/skills/uloop-replay-input/SKILL.md +++ b/.agents/skills/uloop-replay-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-replay-input -description: "Replay recorded input during PlayMode with frame-precise injection. Use when you need to: (1) Reproduce recorded gameplay exactly, (2) Run E2E tests from recorded input, (3) Generate demo videos with consistent input. Deserializes the JSON recording and pushes captured device states back into Mouse.current / Keyboard.current frame-by-frame in PlayMode. Requires PlayMode and the New Input System." +description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- # uloop replay-input @@ -39,6 +39,12 @@ uloop replay-input --action Stop Replay injects the exact same input frame-by-frame, but the game must also be deterministic to produce identical results. If replay output must be compared across runs, read [references/deterministic-replay.md](references/deterministic-replay.md) before interpreting failures. +## Prerequisites + +- Unity must be in **PlayMode** +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. + ## Output Returns JSON with: diff --git a/.agents/skills/uloop-run-tests/SKILL.md b/.agents/skills/uloop-run-tests/SKILL.md index 9f4547659e..78b7d2796e 100644 --- a/.agents/skills/uloop-run-tests/SKILL.md +++ b/.agents/skills/uloop-run-tests/SKILL.md @@ -1,11 +1,13 @@ --- name: uloop-run-tests -description: "Execute Unity Test Runner and get detailed results. Use when you need to: (1) Run EditMode or PlayMode unit tests, (2) Verify code changes pass all tests, (3) Diagnose test failures with error messages and stack traces. Single-flight only — never run multiple `uloop run-tests` in parallel." +description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- # uloop run-tests -Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. +Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. + +When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. @@ -59,7 +61,7 @@ Returns JSON with: - `PassedCount` (number): Passed tests - `FailedCount` (number): Failed tests - `SkippedCount` (number): Skipped tests -- `XmlPath` (string): Path to NUnit XML result file. Empty string when no XML was saved (typically on `Success: true`); populated only when tests failed and the XML file exists on disk. +- `XmlPath` (string | null): Path to NUnit XML result file. `null` when no XML was saved; populated only when tests failed and the XML file exists on disk. ### XML Result File diff --git a/.agents/skills/uloop-screenshot/SKILL.md b/.agents/skills/uloop-screenshot/SKILL.md index 724ecedaa3..9125b7b471 100644 --- a/.agents/skills/uloop-screenshot/SKILL.md +++ b/.agents/skills/uloop-screenshot/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-screenshot -description: "Capture screenshots of Unity Editor windows as PNG files. Use when you need to: (1) Screenshot Game View, Scene View, Console, Inspector, or other windows, (2) Capture current visual state for debugging or documentation, (3) Save editor window appearance as PNG files with optional UI element annotations. Writes PNG files via uloop CLI." +description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- # uloop screenshot @@ -10,7 +10,7 @@ Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--elements-only] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,8 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--elements-only` | boolean | `false` | Return only annotated element JSON without capturing a screenshot image. Requires `--annotate-elements` and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -49,14 +51,24 @@ The window name is the text displayed in the window's title bar (tab). Common na # Take a screenshot of Game View (default) uloop screenshot -# Capture game rendering (coordinates match simulate-mouse, PlayMode required) +# Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +uloop screenshot --capture-mode rendering --annotate-elements true + +# Annotate 3D physics raycast candidate points +uloop screenshot --capture-mode rendering --annotate-raycast-grid true + +# Inspect hit layers, then rerun with the layer used by game input +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true + +# Annotate clustered 3D collider candidates on selected layers +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Take a screenshot of Scene View uloop screenshot --window-name Scene @@ -80,12 +92,17 @@ Returns JSON with: - `FileSizeBytes`: Size of the saved file in bytes - `Width`: Captured image width in pixels - `Height`: Captured image height in pixels - - `CoordinateSystem`: `"gameView"` or `"window"` + - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `YOffset`: Y offset used for gameView coordinate conversion - - `AnnotatedElements`: Array of annotated UI element metadata. Empty unless `--annotate-elements` is used. + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. -For `AnnotatedElements` fields and gameView coordinate conversion, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. +For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. When multiple windows match (e.g., multiple Inspector windows or when using `contains` mode), all matching windows are captured with numbered filenames (e.g., `Inspector_1_*.png`, `Inspector_2_*.png`). @@ -94,3 +111,22 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con - Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. +- Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. +- Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. + +### Raycast Annotation Demo Scene + +Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: + +```bash +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +``` + +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. +- Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/.agents/skills/uloop-screenshot/references/annotated-elements.md b/.agents/skills/uloop-screenshot/references/annotated-elements.md index 283109ccc2..11117162d7 100644 --- a/.agents/skills/uloop-screenshot/references/annotated-elements.md +++ b/.agents/skills/uloop-screenshot/references/annotated-elements.md @@ -1,37 +1,62 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used. Entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name - `Path`: Hierarchy path from the scene root, for example `Canvas/Panel/Button`. Use this as `simulate-mouse-ui --target-path` when bypassing raycast blockers. -- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`) -- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`). Use this to choose between `simulate-mouse-ui --action Click` and drag actions. -- `SimX`, `SimY`: Center position in simulate-mouse coordinates. Use these directly with `--x` and `--y`. -- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates +- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`, `PhysicsCollider`) +- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`) or `Raycast` for clustered physics collider entries. Use this to choose between `simulate-mouse-ui --action Click`, drag actions, or `simulate-mouse-input`/`raycast`. +- `Layer`: Physics layer name for `PhysicsCollider` entries. Empty for UI entries. +- `Components`: Collider and MonoBehaviour component type names from the hit GameObject for `PhysicsCollider` entries. Empty for UI entries. +- `SimX`, `SimY`: Target click position in top-left Game View coordinates. For UI entries this is the element center; for `PhysicsCollider` entries this is a representative sampled hit. Use these directly with `simulate-mouse-ui --x/--y`, `simulate-mouse-input --x/--y`, or `raycast --x/--y`. +- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates. For `PhysicsCollider` entries, this is the axis-aligned sampled-cell coverage box from reachable raycast hits, not a guarantee that every interior point is clickable. - `SortingOrder`: Canvas sorting order. Higher values are in front. - `SiblingIndex`: Transform sibling index under the element's direct parent. Do not use it as a reliable z-order signal across nested UI hierarchies. +## RaycastLayerSummaries Fields + +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. + +- `Layer`: Physics layer name to pass to `--raycast-layer-mask` +- `LayerIndex`: Unity physics layer index +- `HitCount`: Dense raycast hit count for the layer +- `RepresentativeObjectPath`: Hierarchy path for the object with the most hits on that layer. Ties are resolved alphabetically by path. + +Entries are sorted by `HitCount` descending, then `LayerIndex` ascending. + ## Coordinate Conversion -When `CoordinateSystem` is `"gameView"`, convert image pixel coordinates to simulate-mouse coordinates: +When `ImageCoordinateSystem` is `"top-left-game-view"`, convert raw image pixel coordinates from `screenshot --capture-mode rendering` with the formula returned in `ScreenshotToInputFormula`: ```text -sim_x = image_x / ResolutionScale -sim_y = image_y / ResolutionScale + YOffset +input_x = image_x / resolutionScale +input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0`, this simplifies to: +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. + +For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. + +`--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. + +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. + +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. + +The mouse input tools convert internally to Unity Input System coordinates: ```text -sim_x = image_x -sim_y = image_y + YOffset +unity_x = input_x +unity_y = gameViewHeight - input_y ``` +Do not flip Y in the caller. + ## Annotation Readability Annotated screenshots compensate border thickness for `ResolutionScale`, so the saved PNG keeps the intended outline width after downscaling. The neutral contrast borders are 2 output pixels each, and the colored middle border is 4 output pixels. Label outlines are also compensated and are separated from element borders by a 4 output pixel gap. diff --git a/.agents/skills/uloop-simulate-keyboard/SKILL.md b/.agents/skills/uloop-simulate-keyboard/SKILL.md index 574794c166..b14b4d8e0c 100644 --- a/.agents/skills/uloop-simulate-keyboard/SKILL.md +++ b/.agents/skills/uloop-simulate-keyboard/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-keyboard -description: "Simulate keyboard key input in PlayMode via Input System. Use when you need to: (1) Press game control keys like WASD, Space, or Shift during PlayMode, (2) Hold keys down for continuous movement or actions, (3) Combine multiple held keys for complex input like Shift+W for sprint. Injects into Unity Input System (`Keyboard.current`); requires PlayMode and the New Input System." +description: "Simulate keyboard input in PlayMode through Unity Input System. Use for key presses, holds, releases, and game controls such as WASD or Space." context: fork --- @@ -82,6 +82,6 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** (`com.unity.inputsystem`) must be installed -- Active Input Handling must be set to **Input System Package (New)** or **Both** in Player Settings +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.agents/skills/uloop-simulate-mouse-input/SKILL.md b/.agents/skills/uloop-simulate-mouse-input/SKILL.md index 8a4f9e0981..d0bca06e34 100644 --- a/.agents/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-input -description: "Simulate mouse input in PlayMode for gameplay code that reads Unity Input System Mouse.current. Use when you need to: (1) Click or right-click in games that read Mouse.current button state, (2) Inject mouse delta for FPS camera control, (3) Inject scroll wheel for hotbar switching or zoom. Requires PlayMode and the New Input System; for EventSystem UI elements, use simulate-mouse-ui instead." +description: "Simulate Mouse.current input in PlayMode through Unity Input System. Use for gameplay clicks, mouse delta, or scroll; use simulate-mouse-ui for EventSystem UI elements." context: fork --- @@ -11,7 +11,7 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. For Click/LongPress: determine the target screen position (use `uloop screenshot` to find coordinates) +2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` 3. Execute the appropriate `uloop simulate-mouse-input` command 4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 5. Report what happened @@ -27,8 +27,8 @@ uloop simulate-mouse-input --action [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, `Scroll` | -| `--x` | number | `0` | Target X position (origin: top-left). Used by Click and LongPress. | -| `--y` | number | `0` | Target Y position (origin: top-left). Used by Click and LongPress. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | | `--button` | enum | `Left` | Mouse button: `Left`, `Right`, `Middle`. Used by Click and LongPress. | | `--duration` | number | `0` | Hold duration for LongPress, or interpolation duration for SmoothDelta (seconds). For Click, 0 = one-shot tap. | | `--delta-x` | number | `0` | Delta X in pixels for MoveDelta/SmoothDelta. Positive = right. | @@ -58,7 +58,7 @@ uloop simulate-mouse-input --action [options] | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System; otherwise prefer `execute-dynamic-code` for a project-specific workaround | +| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System | | Place a block with right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | | Drag a UI slider | `simulate-mouse-ui --action Drag` | | Look around with mouse (FPS camera) | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | @@ -67,7 +67,7 @@ uloop simulate-mouse-input --action [options] ## Examples ```bash -# Left-click at screen center (for game logic) +# Left-click at the Game View center (for game logic) uloop simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) @@ -89,12 +89,26 @@ uloop simulate-mouse-input --action Scroll --scroll-y -120 uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` +## Coordinate System + +- `--x` / `--y` use **top-left Game View coordinates**. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally for Unity Input System: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +- `Mouse.current.position` uses bottom-left Unity coordinates, so the value read inside Unity may show the converted Y. + ## Prerequisites - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- If the target project cannot use the New Input System, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool +- Use this only when the project already uses the New Input System. ## Output @@ -103,7 +117,12 @@ Returns JSON with: - `Message`: Status message - `Action`: Echoes which action was executed (`Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, or `Scroll`) - `Button`: Which button was used (nullable string; populated for `Click` / `LongPress`, null otherwise) -- `PositionX`: Target X coordinate (nullable float; populated for `Click` / `LongPress`) -- `PositionY`: Target Y coordinate (nullable float; populated for `Click` / `LongPress`) - -These are the only six fields. There is no `DeltaX`, `DeltaY`, `ScrollX`, `ScrollY`, `Duration`, or hit-element field in the response — only the issued action, button, and target position are echoed back. Verify visual outcome with a follow-up screenshot. +- `PositionX` / `PositionY`: Target top-left Game View coordinates (nullable float; populated for `Click` / `LongPress`) +- `InputCoordinateSystem`: `"top-left-game-view"` for click/long-press coordinates +- `UnityCoordinateSystem`: `"bottom-left-game-view"` for the injected `Mouse.current.position` +- `GameViewWidth` / `GameViewHeight`: Game View size used for conversion +- `InputPositionX` / `InputPositionY`: Coordinates received from the caller +- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` +- `CoordinateConversionFormula`: Conversion formula used by the tool + +Verify visual outcome with a follow-up screenshot. diff --git a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md index ffd6160091..95f6185a18 100644 --- a/.agents/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.agents/skills/uloop-simulate-mouse-ui/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-ui -description: "Simulate mouse click, long-press, and drag on PlayMode UI elements via EventSystem screen coordinates from annotated screenshots. Use when you need to: (1) Click buttons or interactive UI elements during PlayMode testing, (2) Drag UI elements between annotated screen positions, (3) Long-press or hold a drag for sustained pointer interactions. First get target coordinates with `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` and use `AnnotatedElements[].SimX` / `SimY`; for gameplay code that reads Mouse.current, use simulate-mouse-input instead." +description: "Simulate PlayMode EventSystem UI mouse actions using top-left Game View coordinates. Use for UI clicks, long-presses, or drags from annotated screenshots." context: fork --- @@ -11,10 +11,10 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. 4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference @@ -28,10 +28,10 @@ uloop simulate-mouse-ui --action --x --y [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `Drag`, `DragStart`, `DragMove`, `DragEnd`, `LongPress` | -| `--x` | number | `0` | Target X position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--y` | number | `0` | Target Y position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--from-x` | number | `0` | Start X position for Drag action. Drag starts here and moves to x,y. | -| `--from-y` | number | `0` | Start Y position for Drag action. Drag starts here and moves to x,y. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--from-x` | number | `0` | Start X position in Game View pixels for Drag action. Drag starts here and moves to x,y. | +| `--from-y` | number | `0` | Start Y position in Game View pixels for Drag action. Drag starts here and moves to x,y. | | `--drag-speed` | number | `2000` | Drag speed in pixels per second (0 for instant). 2000 is fast (default), 200 is slow enough to watch. Applies to Drag, DragMove, and DragEnd actions. | | `--duration` | number | `0.5` | Hold duration in seconds for LongPress action. | | `--button` | enum | `Left` | Mouse button. `Click` and `LongPress` support `Left`, `Right`, and `Middle`. Drag actions support `Left` only; other buttons return an error. | @@ -67,7 +67,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Coordinate System - Origin is **top-left** (0, 0) -- All positions are in **screen pixels** +- All positions are in **top-left Game View pixels** - Get coordinates from `AnnotatedElements` JSON (`SimX`/`SimY`) — do NOT look up GameObject positions - Clicking or long-pressing on empty space (no UI element) still succeeds with a message indicating no element was hit - Dragging on empty space (no draggable UI element) returns `Success = false` @@ -77,7 +77,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Examples ```bash -# Click a button at screen position +# Click a button at a Game View position uloop simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. diff --git a/.claude/skills/uloop-clear-console/SKILL.md b/.claude/skills/uloop-clear-console/SKILL.md index 6ba18c90f3..40891bc388 100644 --- a/.claude/skills/uloop-clear-console/SKILL.md +++ b/.claude/skills/uloop-clear-console/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-clear-console -description: "Clear all Unity Console log entries. Use when you need to: (1) Clear console before running tests or compilation, (2) Start a fresh debugging session, (3) Remove noisy logs to isolate specific output." +description: "Clear Unity Console entries. Use before compile, tests, or debugging when stale logs would hide the current result." --- # uloop clear-console diff --git a/.claude/skills/uloop-compile/SKILL.md b/.claude/skills/uloop-compile/SKILL.md index f13d06abd1..0f6846d80d 100644 --- a/.claude/skills/uloop-compile/SKILL.md +++ b/.claude/skills/uloop-compile/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-compile -description: "Compile Unity project and report errors/warnings. Use when you need to: (1) Verify code compiles after C# file edits, (2) Check for compile errors before testing, (3) Force full recompilation with Domain Reload. Returns error and warning counts." +description: "Compile the Unity project and report errors/warnings. Use after C# edits or when a full Domain Reload compile is needed." --- # uloop compile @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/.claude/skills/uloop-control-play-mode/SKILL.md b/.claude/skills/uloop-control-play-mode/SKILL.md index 220eeebe91..5f830696ec 100644 --- a/.claude/skills/uloop-control-play-mode/SKILL.md +++ b/.claude/skills/uloop-control-play-mode/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-control-play-mode -description: "Control Unity Editor play mode (play/stop/pause). Use when you need to: (1) Start play mode to test game behavior, (2) Stop play mode to return to edit mode, (3) Pause play mode for frame-by-frame inspection." +description: "Control Unity Editor Play Mode. Use to start, stop, or pause Play Mode for runtime behavior checks and frame inspection." --- # uloop control-play-mode diff --git a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md index de0ed73fc1..31ddf7adc1 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/asset-operations.md @@ -184,6 +184,8 @@ return $"Asset has {dependencies.Length} dependencies"; ## Refresh AssetDatabase +Use this after terminal-based asset file changes when Unity needs to import them and generate `.meta` files. + ```csharp using UnityEditor; diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md index e14ceca3d9..15c34ac072 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md @@ -177,7 +177,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```powershell -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-components true +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md index d82b7c6149..b1d94797db 100644 --- a/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md +++ b/.claude/skills/uloop-execute-dynamic-code/references/playmode-automation-zsh.md @@ -146,7 +146,7 @@ Use `find-game-objects` to discover buttons with their hierarchy paths, then cli **Step 1**: Find all GameObjects with Button component ```zsh -uloop find-game-objects --required-components UnityEngine.UI.Button --include-inactive false +uloop find-game-objects --required-components UnityEngine.UI.Button ``` **Step 2**: Click the target button using the path from Step 1 @@ -172,7 +172,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```zsh -uloop get-hierarchy --root-path "Canvas" --max-depth 3 --include-components true +uloop get-hierarchy --root-path "Canvas" --max-depth 3 ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/.claude/skills/uloop-find-game-objects/SKILL.md b/.claude/skills/uloop-find-game-objects/SKILL.md index 9c809e658c..d7084b4a91 100644 --- a/.claude/skills/uloop-find-game-objects/SKILL.md +++ b/.claude/skills/uloop-find-game-objects/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-find-game-objects -description: "Use first when the user asks about the currently selected GameObject in the Unity Hierarchy. Inspect selected object details with `--search-mode Selected` before using `execute-dynamic-code`. Use when you need to: (1) Get details and component properties for selected GameObject(s), (2) Search for objects by name, regex, or path, (3) Find objects with specific components, tags, or layers. Use get-hierarchy when the child tree under the selection is needed. Returns hierarchy paths, active state, tags, layers, and components (or writes to a file when multiple GameObjects are selected)." +description: "Find or inspect Unity GameObjects, especially objects the user currently selected in the Hierarchy. Use for details, components, tags, layers, or name/path searches." --- # uloop find-game-objects diff --git a/.claude/skills/uloop-focus-window/SKILL.md b/.claude/skills/uloop-focus-window/SKILL.md index 87dbc35876..9379ef080b 100644 --- a/.claude/skills/uloop-focus-window/SKILL.md +++ b/.claude/skills/uloop-focus-window/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-focus-window -description: "Bring Unity Editor window to front via uloop CLI. Use when you need to: (1) Focus Unity Editor before capturing screenshots, (2) Ensure Unity window is visible for visual checks, (3) Bring Unity to foreground for user interaction." +description: "Bring the Unity Editor window to front. Use when Unity must be visible for visual checks or user-facing interaction." --- # uloop focus-window diff --git a/.claude/skills/uloop-get-hierarchy/SKILL.md b/.claude/skills/uloop-get-hierarchy/SKILL.md index 976e0de5a6..8a5a932952 100644 --- a/.claude/skills/uloop-get-hierarchy/SKILL.md +++ b/.claude/skills/uloop-get-hierarchy/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-get-hierarchy -description: "Get Unity scene hierarchy as a structured tree from all roots, a root path, or the current Hierarchy selection. Use this when you need the child tree, parent-child structure, or descendants under selected GameObject(s) with `--use-selection`. Use find-game-objects for selected object details and component properties. Hierarchy data is written to a JSON file on disk and the response returns the file path (not the tree inline) — open the file to read the structure." +description: "Get the Unity scene hierarchy as a structured tree. Use for parent-child structure, descendants, roots, or subtrees under objects the user currently selected." --- # uloop get-hierarchy diff --git a/.claude/skills/uloop-get-logs/SKILL.md b/.claude/skills/uloop-get-logs/SKILL.md index 1ca03fc116..7cb5f5f311 100644 --- a/.claude/skills/uloop-get-logs/SKILL.md +++ b/.claude/skills/uloop-get-logs/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-get-logs -description: "Retrieve current Unity Console entries via uloop CLI. Use when you need to: (1) inspect errors, warnings, or logs after compile, tests, PlayMode, or dynamic code execution, (2) search current Console messages or stack traces, (3) confirm whether a recent Unity operation emitted logs. Prefer this over reading Editor.log or Unity log files for normal Console contents; use log files only for startup, crash, freeze, or uloop connection failures." +description: "Read current Unity Console entries from a running Editor. Use during bug investigation after compile, tests, PlayMode, or dynamic code to inspect logs, warnings, errors, and stack traces." --- # uloop get-logs diff --git a/.claude/skills/uloop-launch/SKILL.md b/.claude/skills/uloop-launch/SKILL.md index 411f481ba0..bedf8464ed 100644 --- a/.claude/skills/uloop-launch/SKILL.md +++ b/.claude/skills/uloop-launch/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-launch -description: "Launch Unity project with matching Editor version via uloop CLI. Use when you need to: (1) Open a Unity project with the correct Editor version, (2) Restart Unity to apply changes, (3) Switch build target when launching." +description: "Use when Unity Editor is not running or needs a clean restart." --- # uloop launch diff --git a/.claude/skills/uloop-raycast/SKILL.md b/.claude/skills/uloop-raycast/SKILL.md new file mode 100644 index 0000000000..b464ae09c3 --- /dev/null +++ b/.claude/skills/uloop-raycast/SKILL.md @@ -0,0 +1,61 @@ +--- +name: uloop-raycast +description: "Raycast from Camera.main through a Game View coordinate. Use when you need to check what a screenshot coordinate would hit in 3D physics before clicking or long-pressing with simulate-mouse-input." +--- + +# uloop raycast + +Check what a top-left Game View coordinate hits in 3D physics. + +## Usage + +```bash +uloop raycast --x --y [--layer-mask ] [--max-distance ] +``` + +## Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--layer-mask` | number | Unity default raycast layers | Physics layer mask used by the raycast. | +| `--max-distance` | number | `1000` | Maximum raycast distance in world units. | + +## Coordinate System + +- `--x` / `--y` use the same top-left Game View input coordinates as `simulate-mouse-input`. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +## Examples + +```bash +# Check what is under a screenshot coordinate +uloop raycast --x 960 --y 540 + +# Check only specific layers +uloop raycast --x 960 --y 540 --layer-mask 1 +``` + +## Output + +Returns JSON with: +- `Success`: Whether the command completed +- `Message`: Status message +- `Hit`: Whether physics hit anything +- `HitGameObjectName` / `HitGameObjectPath`: Hit object identity when `Hit` is true +- `HitLayer` / `HitLayerName`: Hit object layer when `Hit` is true +- `Distance`, `HitPointX/Y/Z`, `HitNormalX/Y/Z`: Hit details when `Hit` is true +- `InputCoordinateSystem`, `UnityCoordinateSystem`, `GameViewWidth/Height`, `InputPositionX/Y`, `InjectedUnityPositionX/Y`, `CoordinateConversionFormula`: Coordinate conversion details + +## Notes + +- Requires an active `Camera.main`. +- Uses Unity Physics raycasts, not UI EventSystem raycasts. diff --git a/.claude/skills/uloop-record-input/SKILL.md b/.claude/skills/uloop-record-input/SKILL.md index 9e00ebcfcb..bfb60f7721 100644 --- a/.claude/skills/uloop-record-input/SKILL.md +++ b/.claude/skills/uloop-record-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-record-input -description: "Record keyboard and mouse input during PlayMode into a JSON file. Use when you need to: (1) Capture human gameplay input for later replay, (2) Record input sequences for E2E testing, (3) Save input for bug reproduction. Captures Input System device-state diffs frame-by-frame in PlayMode and serializes them to JSON when stopped. Requires PlayMode and the New Input System." +description: "Record PlayMode keyboard and mouse input to JSON. Use to capture gameplay, bug repro, or E2E input sequences for replay." --- # uloop record-input @@ -39,7 +39,7 @@ Replay injects input frame-by-frame, so the game must also be deterministic to p - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) -- Active Input Handling must be set to `Input System Package (New)` or `Both` in Player Settings +- Use this only when the project already uses the New Input System. ## Output diff --git a/.claude/skills/uloop-replay-input/SKILL.md b/.claude/skills/uloop-replay-input/SKILL.md index 1c9cb00081..e4a103fc20 100644 --- a/.claude/skills/uloop-replay-input/SKILL.md +++ b/.claude/skills/uloop-replay-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-replay-input -description: "Replay recorded input during PlayMode with frame-precise injection. Use when you need to: (1) Reproduce recorded gameplay exactly, (2) Run E2E tests from recorded input, (3) Generate demo videos with consistent input. Deserializes the JSON recording and pushes captured device states back into Mouse.current / Keyboard.current frame-by-frame in PlayMode. Requires PlayMode and the New Input System." +description: "Replay recorded PlayMode keyboard and mouse input. Use for exact gameplay reproduction, E2E runs, or consistent demos from JSON recordings." --- # uloop replay-input @@ -39,6 +39,12 @@ uloop replay-input --action Stop Replay injects the exact same input frame-by-frame, but the game must also be deterministic to produce identical results. If replay output must be compared across runs, read [references/deterministic-replay.md](references/deterministic-replay.md) before interpreting failures. +## Prerequisites + +- Unity must be in **PlayMode** +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. + ## Output Returns JSON with: diff --git a/.claude/skills/uloop-run-tests/SKILL.md b/.claude/skills/uloop-run-tests/SKILL.md index 9f4547659e..78b7d2796e 100644 --- a/.claude/skills/uloop-run-tests/SKILL.md +++ b/.claude/skills/uloop-run-tests/SKILL.md @@ -1,11 +1,13 @@ --- name: uloop-run-tests -description: "Execute Unity Test Runner and get detailed results. Use when you need to: (1) Run EditMode or PlayMode unit tests, (2) Verify code changes pass all tests, (3) Diagnose test failures with error messages and stack traces. Single-flight only — never run multiple `uloop run-tests` in parallel." +description: "Run Unity Test Runner and report detailed results. Use for EditMode/PlayMode tests, change verification, or failure diagnosis." --- # uloop run-tests -Execute Unity Test Runner. When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. +Execute Unity Test Runner. This command requires the Unity Test Framework package (`com.unity.test-framework`). If that package is not installed, the command returns `Success: false` with an unsupported message and does not affect the other Unity CLI Loop tools. + +When tests fail, NUnit XML results with error messages and stack traces are automatically saved. Read the XML file at `XmlPath` for detailed failure diagnosis. Before executing tests, `uloop run-tests` checks for unsaved loaded Scene changes and unsaved current Prefab Stage changes. If any are found, it returns `Success: false`, keeps `TestCount` at `0`, lists the unsaved items in `Message`, and does not start the Unity Test Runner. Save or discard those editor changes, then rerun the command. Use `--save-before-run true` only when the user explicitly asks to save editor changes before continuing. @@ -59,7 +61,7 @@ Returns JSON with: - `PassedCount` (number): Passed tests - `FailedCount` (number): Failed tests - `SkippedCount` (number): Skipped tests -- `XmlPath` (string): Path to NUnit XML result file. Empty string when no XML was saved (typically on `Success: true`); populated only when tests failed and the XML file exists on disk. +- `XmlPath` (string | null): Path to NUnit XML result file. `null` when no XML was saved; populated only when tests failed and the XML file exists on disk. ### XML Result File diff --git a/.claude/skills/uloop-screenshot/SKILL.md b/.claude/skills/uloop-screenshot/SKILL.md index 724ecedaa3..9125b7b471 100644 --- a/.claude/skills/uloop-screenshot/SKILL.md +++ b/.claude/skills/uloop-screenshot/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-screenshot -description: "Capture screenshots of Unity Editor windows as PNG files. Use when you need to: (1) Screenshot Game View, Scene View, Console, Inspector, or other windows, (2) Capture current visual state for debugging or documentation, (3) Save editor window appearance as PNG files with optional UI element annotations. Writes PNG files via uloop CLI." +description: "Capture Unity Editor windows or Game View rendering as PNG. Use for visual checks, debugging, documentation, or annotated UI element coordinates." --- # uloop screenshot @@ -10,7 +10,7 @@ Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--elements-only] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,8 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--elements-only` | boolean | `false` | Return only annotated element JSON without capturing a screenshot image. Requires `--annotate-elements` and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -49,14 +51,24 @@ The window name is the text displayed in the window's title bar (tab). Common na # Take a screenshot of Game View (default) uloop screenshot -# Capture game rendering (coordinates match simulate-mouse, PlayMode required) +# Capture game rendering (default scale coordinates match simulate-mouse, PlayMode required) uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +uloop screenshot --capture-mode rendering --annotate-elements true + +# Annotate 3D physics raycast candidate points +uloop screenshot --capture-mode rendering --annotate-raycast-grid true + +# Inspect hit layers, then rerun with the layer used by game input +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true + +# Annotate clustered 3D collider candidates on selected layers +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Take a screenshot of Scene View uloop screenshot --window-name Scene @@ -80,12 +92,17 @@ Returns JSON with: - `FileSizeBytes`: Size of the saved file in bytes - `Width`: Captured image width in pixels - `Height`: Captured image height in pixels - - `CoordinateSystem`: `"gameView"` or `"window"` + - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `YOffset`: Y offset used for gameView coordinate conversion - - `AnnotatedElements`: Array of annotated UI element metadata. Empty unless `--annotate-elements` is used. + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. -For `AnnotatedElements` fields and gameView coordinate conversion, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. +For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. When multiple windows match (e.g., multiple Inspector windows or when using `contains` mode), all matching windows are captured with numbered filenames (e.g., `Inspector_1_*.png`, `Inspector_2_*.png`). @@ -94,3 +111,22 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con - Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. +- Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. +- Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. + +### Raycast Annotation Demo Scene + +Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fixture for raycast annotation checks. Enter PlayMode, then run: + +```bash +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 +``` + +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. +- Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/.claude/skills/uloop-screenshot/references/annotated-elements.md b/.claude/skills/uloop-screenshot/references/annotated-elements.md index 283109ccc2..11117162d7 100644 --- a/.claude/skills/uloop-screenshot/references/annotated-elements.md +++ b/.claude/skills/uloop-screenshot/references/annotated-elements.md @@ -1,37 +1,62 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used. Entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name - `Path`: Hierarchy path from the scene root, for example `Canvas/Panel/Button`. Use this as `simulate-mouse-ui --target-path` when bypassing raycast blockers. -- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`) -- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`). Use this to choose between `simulate-mouse-ui --action Click` and drag actions. -- `SimX`, `SimY`: Center position in simulate-mouse coordinates. Use these directly with `--x` and `--y`. -- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates +- `Type`: Element type (`Button`, `Toggle`, `Slider`, `Dropdown`, `InputField`, `Scrollbar`, `Draggable`, `DropTarget`, `Selectable`, `PhysicsCollider`) +- `Interaction`: Derived interaction category (`Click`, `Drag`, `Drop`, `Text`) or `Raycast` for clustered physics collider entries. Use this to choose between `simulate-mouse-ui --action Click`, drag actions, or `simulate-mouse-input`/`raycast`. +- `Layer`: Physics layer name for `PhysicsCollider` entries. Empty for UI entries. +- `Components`: Collider and MonoBehaviour component type names from the hit GameObject for `PhysicsCollider` entries. Empty for UI entries. +- `SimX`, `SimY`: Target click position in top-left Game View coordinates. For UI entries this is the element center; for `PhysicsCollider` entries this is a representative sampled hit. Use these directly with `simulate-mouse-ui --x/--y`, `simulate-mouse-input --x/--y`, or `raycast --x/--y`. +- `BoundsMinX`, `BoundsMinY`, `BoundsMaxX`, `BoundsMaxY`: Bounding box in simulate-mouse coordinates. For `PhysicsCollider` entries, this is the axis-aligned sampled-cell coverage box from reachable raycast hits, not a guarantee that every interior point is clickable. - `SortingOrder`: Canvas sorting order. Higher values are in front. - `SiblingIndex`: Transform sibling index under the element's direct parent. Do not use it as a reliable z-order signal across nested UI hierarchies. +## RaycastLayerSummaries Fields + +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. + +- `Layer`: Physics layer name to pass to `--raycast-layer-mask` +- `LayerIndex`: Unity physics layer index +- `HitCount`: Dense raycast hit count for the layer +- `RepresentativeObjectPath`: Hierarchy path for the object with the most hits on that layer. Ties are resolved alphabetically by path. + +Entries are sorted by `HitCount` descending, then `LayerIndex` ascending. + ## Coordinate Conversion -When `CoordinateSystem` is `"gameView"`, convert image pixel coordinates to simulate-mouse coordinates: +When `ImageCoordinateSystem` is `"top-left-game-view"`, convert raw image pixel coordinates from `screenshot --capture-mode rendering` with the formula returned in `ScreenshotToInputFormula`: ```text -sim_x = image_x / ResolutionScale -sim_y = image_y / ResolutionScale + YOffset +input_x = image_x / resolutionScale +input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0`, this simplifies to: +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. + +For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. + +`--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. + +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. + +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. + +The mouse input tools convert internally to Unity Input System coordinates: ```text -sim_x = image_x -sim_y = image_y + YOffset +unity_x = input_x +unity_y = gameViewHeight - input_y ``` +Do not flip Y in the caller. + ## Annotation Readability Annotated screenshots compensate border thickness for `ResolutionScale`, so the saved PNG keeps the intended outline width after downscaling. The neutral contrast borders are 2 output pixels each, and the colored middle border is 4 output pixels. Label outlines are also compensated and are separated from element borders by a 4 output pixel gap. diff --git a/.claude/skills/uloop-simulate-keyboard/SKILL.md b/.claude/skills/uloop-simulate-keyboard/SKILL.md index 574794c166..b14b4d8e0c 100644 --- a/.claude/skills/uloop-simulate-keyboard/SKILL.md +++ b/.claude/skills/uloop-simulate-keyboard/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-keyboard -description: "Simulate keyboard key input in PlayMode via Input System. Use when you need to: (1) Press game control keys like WASD, Space, or Shift during PlayMode, (2) Hold keys down for continuous movement or actions, (3) Combine multiple held keys for complex input like Shift+W for sprint. Injects into Unity Input System (`Keyboard.current`); requires PlayMode and the New Input System." +description: "Simulate keyboard input in PlayMode through Unity Input System. Use for key presses, holds, releases, and game controls such as WASD or Space." context: fork --- @@ -82,6 +82,6 @@ Returns JSON with: ## Prerequisites - Unity must be in **PlayMode** -- **Input System package** (`com.unity.inputsystem`) must be installed -- Active Input Handling must be set to **Input System Package (New)** or **Both** in Player Settings +- **Input System package** must be installed (`com.unity.inputsystem`) +- Use this only when the project already uses the New Input System. - Game code must read input via Input System API (e.g. `Keyboard.current[Key.W].isPressed`), not legacy `Input.GetKey()` diff --git a/.claude/skills/uloop-simulate-mouse-input/SKILL.md b/.claude/skills/uloop-simulate-mouse-input/SKILL.md index 8a4f9e0981..d0bca06e34 100644 --- a/.claude/skills/uloop-simulate-mouse-input/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-input/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-input -description: "Simulate mouse input in PlayMode for gameplay code that reads Unity Input System Mouse.current. Use when you need to: (1) Click or right-click in games that read Mouse.current button state, (2) Inject mouse delta for FPS camera control, (3) Inject scroll wheel for hotbar switching or zoom. Requires PlayMode and the New Input System; for EventSystem UI elements, use simulate-mouse-ui instead." +description: "Simulate Mouse.current input in PlayMode through Unity Input System. Use for gameplay clicks, mouse delta, or scroll; use simulate-mouse-ui for EventSystem UI elements." context: fork --- @@ -11,7 +11,7 @@ Simulate mouse input via Input System in Unity PlayMode: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. For Click/LongPress: determine the target screen position (use `uloop screenshot` to find coordinates) +2. For Click/LongPress: determine the target Game View input position from annotated `SimX`/`SimY`, raycast-grid `InputX`/`InputY`, or raw image pixels converted with `ScreenshotToInputFormula` 3. Execute the appropriate `uloop simulate-mouse-input` command 4. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering` 5. Report what happened @@ -27,8 +27,8 @@ uloop simulate-mouse-input --action [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, `Scroll` | -| `--x` | number | `0` | Target X position (origin: top-left). Used by Click and LongPress. | -| `--y` | number | `0` | Target Y position (origin: top-left). Used by Click and LongPress. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimX`, `RaycastGridPoints[].InputX`, or raw image pixels converted with `ScreenshotToInputFormula`. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click and LongPress. Use `AnnotatedElements[].SimY`, `RaycastGridPoints[].InputY`, or raw image pixels converted with `ScreenshotToInputFormula`. | | `--button` | enum | `Left` | Mouse button: `Left`, `Right`, `Middle`. Used by Click and LongPress. | | `--duration` | number | `0` | Hold duration for LongPress, or interpolation duration for SmoothDelta (seconds). For Click, 0 = one-shot tap. | | `--delta-x` | number | `0` | Delta X in pixels for MoveDelta/SmoothDelta. Positive = right. | @@ -58,7 +58,7 @@ uloop simulate-mouse-input --action [options] | Scenario | Tool | |----------|------| | Click a Unity UI Button (IPointerClickHandler) | `simulate-mouse-ui` | -| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System; otherwise prefer `execute-dynamic-code` for a project-specific workaround | +| Destroy a block in Minecraft (reads `Mouse.current.leftButton`) | `simulate-mouse-input` when the project uses the New Input System | | Place a block with right-click | `simulate-mouse-input --button Right` when the project uses the New Input System | | Drag a UI slider | `simulate-mouse-ui --action Drag` | | Look around with mouse (FPS camera) | `simulate-mouse-input --action MoveDelta` when the project uses the New Input System | @@ -67,7 +67,7 @@ uloop simulate-mouse-input --action [options] ## Examples ```bash -# Left-click at screen center (for game logic) +# Left-click at the Game View center (for game logic) uloop simulate-mouse-input --action Click --x 400 --y 300 # Right-click at screen center (e.g. place block) @@ -89,12 +89,26 @@ uloop simulate-mouse-input --action Scroll --scroll-y -120 uloop simulate-mouse-input --action SmoothDelta --delta-x 300 --delta-y 0 --duration 0.5 ``` +## Coordinate System + +- `--x` / `--y` use **top-left Game View coordinates**. +- Raw image pixels from `uloop screenshot --capture-mode rendering` must be converted with `ScreenshotToInputFormula`. +- `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` can be passed directly to this tool. +- Do not flip Y in the caller. The tool converts internally for Unity Input System: + +```text +unity_x = input_x +unity_y = gameViewHeight - input_y +``` + +- `Mouse.current.position` uses bottom-left Unity coordinates, so the value read inside Unity may show the converted Y. + ## Prerequisites - Unity must be in **PlayMode** - **Input System package** must be installed (`com.unity.inputsystem`) - Game code must read input via Input System API (e.g. `Mouse.current.leftButton.wasPressedThisFrame`) -- If the target project cannot use the New Input System, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool +- Use this only when the project already uses the New Input System. ## Output @@ -103,7 +117,12 @@ Returns JSON with: - `Message`: Status message - `Action`: Echoes which action was executed (`Click`, `LongPress`, `MoveDelta`, `SmoothDelta`, or `Scroll`) - `Button`: Which button was used (nullable string; populated for `Click` / `LongPress`, null otherwise) -- `PositionX`: Target X coordinate (nullable float; populated for `Click` / `LongPress`) -- `PositionY`: Target Y coordinate (nullable float; populated for `Click` / `LongPress`) - -These are the only six fields. There is no `DeltaX`, `DeltaY`, `ScrollX`, `ScrollY`, `Duration`, or hit-element field in the response — only the issued action, button, and target position are echoed back. Verify visual outcome with a follow-up screenshot. +- `PositionX` / `PositionY`: Target top-left Game View coordinates (nullable float; populated for `Click` / `LongPress`) +- `InputCoordinateSystem`: `"top-left-game-view"` for click/long-press coordinates +- `UnityCoordinateSystem`: `"bottom-left-game-view"` for the injected `Mouse.current.position` +- `GameViewWidth` / `GameViewHeight`: Game View size used for conversion +- `InputPositionX` / `InputPositionY`: Coordinates received from the caller +- `InjectedUnityPositionX` / `InjectedUnityPositionY`: Coordinates injected into `Mouse.current.position` +- `CoordinateConversionFormula`: Conversion formula used by the tool + +Verify visual outcome with a follow-up screenshot. diff --git a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md index ffd6160091..95f6185a18 100644 --- a/.claude/skills/uloop-simulate-mouse-ui/SKILL.md +++ b/.claude/skills/uloop-simulate-mouse-ui/SKILL.md @@ -1,6 +1,6 @@ --- name: uloop-simulate-mouse-ui -description: "Simulate mouse click, long-press, and drag on PlayMode UI elements via EventSystem screen coordinates from annotated screenshots. Use when you need to: (1) Click buttons or interactive UI elements during PlayMode testing, (2) Drag UI elements between annotated screen positions, (3) Long-press or hold a drag for sustained pointer interactions. First get target coordinates with `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` and use `AnnotatedElements[].SimX` / `SimY`; for gameplay code that reads Mouse.current, use simulate-mouse-input instead." +description: "Simulate PlayMode EventSystem UI mouse actions using top-left Game View coordinates. Use for UI clicks, long-presses, or drags from annotated screenshots." context: fork --- @@ -11,10 +11,10 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. 4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference @@ -28,10 +28,10 @@ uloop simulate-mouse-ui --action --x --y [options] | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `--action` | enum | `Click` | `Click`, `Drag`, `DragStart`, `DragMove`, `DragEnd`, `LongPress` | -| `--x` | number | `0` | Target X position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--y` | number | `0` | Target Y position in screen pixels (origin: top-left). For Drag action, this is the destination. | -| `--from-x` | number | `0` | Start X position for Drag action. Drag starts here and moves to x,y. | -| `--from-y` | number | `0` | Start Y position for Drag action. Drag starts here and moves to x,y. | +| `--x` | number | `0` | Target X position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--y` | number | `0` | Target Y position in Game View pixels (origin: top-left). Used by Click, LongPress, DragStart, DragMove, and DragEnd; for Drag, this is the destination. | +| `--from-x` | number | `0` | Start X position in Game View pixels for Drag action. Drag starts here and moves to x,y. | +| `--from-y` | number | `0` | Start Y position in Game View pixels for Drag action. Drag starts here and moves to x,y. | | `--drag-speed` | number | `2000` | Drag speed in pixels per second (0 for instant). 2000 is fast (default), 200 is slow enough to watch. Applies to Drag, DragMove, and DragEnd actions. | | `--duration` | number | `0.5` | Hold duration in seconds for LongPress action. | | `--button` | enum | `Left` | Mouse button. `Click` and `LongPress` support `Left`, `Right`, and `Middle`. Drag actions support `Left` only; other buttons return an error. | @@ -67,7 +67,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Coordinate System - Origin is **top-left** (0, 0) -- All positions are in **screen pixels** +- All positions are in **top-left Game View pixels** - Get coordinates from `AnnotatedElements` JSON (`SimX`/`SimY`) — do NOT look up GameObject positions - Clicking or long-pressing on empty space (no UI element) still succeeds with a message indicating no element was hit - Dragging on empty space (no draggable UI element) returns `Success = false` @@ -77,7 +77,7 @@ uloop simulate-mouse-ui --action --x --y [options] ## Examples ```bash -# Click a button at screen position +# Click a button at a Game View position uloop simulate-mouse-ui --action Click --x 400 --y 300 # Force-click a button behind a raycast blocker by path @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity new file mode 100644 index 0000000000..51e4e1dbc5 --- /dev/null +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity @@ -0,0 +1,2335 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &32857930 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 32857931} + - component: {fileID: 32857933} + - component: {fileID: 32857932} + m_Layer: 0 + m_Name: Blocker Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &32857931 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 344266733} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 380, y: 80} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &32857932 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 26 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'GraphicRaycaster UI blocker + + covered cells should be omitted' +--- !u!222 &32857933 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 32857930} + m_CullTransparentMesh: 1 +--- !u!1 &84949467 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 84949471} + - component: {fileID: 84949470} + - component: {fileID: 84949469} + - component: {fileID: 84949468} + m_Layer: 0 + m_Name: Perspective GraphicRaycaster UI Blocker Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &84949468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!114 &84949469 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!223 &84949470 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 20 + m_TargetDisplay: 0 +--- !u!224 &84949471 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 84949467} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 344266733} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1 &114960129 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 114960130} + - component: {fileID: 114960132} + - component: {fileID: 114960131} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_3_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &114960130 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 2.85, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &114960131 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &114960132 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 114960129} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &219972445 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 219972446} + - component: {fileID: 219972448} + - component: {fileID: 219972447} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_4_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &219972446 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 3.8, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &219972447 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &219972448 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 219972445} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &344266732 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 344266733} + - component: {fileID: 344266735} + - component: {fileID: 344266734} + m_Layer: 0 + m_Name: Center Raycast Blocker + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &344266733 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 32857931} + m_Father: {fileID: 84949471} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 70, y: -20} + m_SizeDelta: {x: 420, y: 260} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &344266734 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.05, g: 0.55, b: 1, a: 0.38} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &344266735 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 344266732} + m_CullTransparentMesh: 1 +--- !u!1 &612736413 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 612736414} + - component: {fileID: 612736416} + - component: {fileID: 612736415} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_1_2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &612736414 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 1.9} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &612736415 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &612736416 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 612736413} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &668389980 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 668389981} + - component: {fileID: 668389983} + - component: {fileID: 668389982} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_1_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &668389981 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &668389982 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &668389983 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 668389980} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!21 &703760602 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Reachable Placement Green + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.1, g: 1, b: 0.28, a: 0.82} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &750890959 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 750890961} + - component: {fileID: 750890960} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!20 &750890960 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 750890959} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.06, g: 0.07, b: 0.08, a: 1} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.1 + far clip plane: 100 + field of view: 43 + orthographic: 0 + orthographic size: 5 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &750890961 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 750890959} + serializedVersion: 2 + m_LocalRotation: {x: 0.23793738, y: -0.32292682, z: 0.084237, w: 0.9121449} + m_LocalPosition: {x: 6.8, y: 6.2, z: -8.4} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &901106301 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 901106304} + - component: {fileID: 901106303} + - component: {fileID: 901106302} + m_Layer: 0 + m_Name: Dark Perspective Platform + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &901106302 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2058516905} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &901106303 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &901106304 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 901106301} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.08, z: 0} + m_LocalScale: {x: 9, y: 0.1, z: 8} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &902889315 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 902889316} + - component: {fileID: 902889318} + - component: {fileID: 902889317} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &902889316 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 1.9} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &902889317 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &902889318 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 902889315} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &909518690 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 909518691} + - component: {fileID: 909518693} + - component: {fileID: 909518692} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &909518691 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0.95} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &909518692 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &909518693 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 909518690} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!21 &1080945992 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Visual Foreground Blocker + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _SURFACE_TYPE_TRANSPARENT + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: + RenderType: Transparent + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 10 + - _DstBlendAlpha: 10 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 5 + - _SrcBlendAlpha: 1 + - _Surface: 1 + - _WorkflowMode: 1 + - _ZWrite: 0 + m_Colors: + - _BaseColor: {r: 1, g: 0.68, b: 0.05, a: 0.38} + - _Color: {r: 1, g: 0.68, b: 0.049999982, a: 0.38} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &1199636759 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1199636760} + - component: {fileID: 1199636762} + - component: {fileID: 1199636761} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_2_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1199636760 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.9, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1199636761 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1199636762 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1199636759} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1241248864 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1241248866} + - component: {fileID: 1241248865} + m_Layer: 0 + m_Name: Key Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1241248865 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1241248864} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 1.35 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 0 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &1241248866 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1241248864} + serializedVersion: 2 + m_LocalRotation: {x: 0.34016195, y: -0.33200905, z: 0.25709796, w: 0.8414039} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1320790928 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1320790929} + - component: {fileID: 1320790931} + - component: {fileID: 1320790930} + m_Layer: 0 + m_Name: PerspectiveSplitGrid_VisualCell_0_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1320790929 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1940938438} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1320790930 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1320790931 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1320790928} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1331696911 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1331696912} + - component: {fileID: 1331696914} + - component: {fileID: 1331696913} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_1_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1331696912 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.95, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1331696913 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1331696914 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1331696911} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1808396404 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1808396408} + - component: {fileID: 1808396407} + - component: {fileID: 1808396406} + - component: {fileID: 1808396405} + m_Layer: 2 + m_Name: Foreground Visual Blocker IgnoreRaycast + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &1808396405 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 1080945992} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!65 &1808396406 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} +--- !u!33 &1808396407 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1808396408 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1808396404} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.15643448, z: 0, w: 0.98768836} + m_LocalPosition: {x: 0.5, y: 0.9, z: -1.25} + m_LocalScale: {x: 4.1, y: 1.8, z: 0.22} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1863570092 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1863570095} + - component: {fileID: 1863570094} + - component: {fileID: 1863570093} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1863570093 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &1863570094 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1863570095 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1863570092} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1940938437 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1940938438} + - component: {fileID: 1940938442} + - component: {fileID: 1940938441} + - component: {fileID: 1940938440} + - component: {fileID: 1940938439} + m_Layer: 0 + m_Name: PerspectiveSplitGrid + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1940938438 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0.2079117, z: 0, w: 0.9781476} + m_LocalPosition: {x: 2.6, y: 0, z: 1.2} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1320790929} + - {fileID: 668389981} + - {fileID: 114960130} + - {fileID: 219972446} + m_Father: {fileID: 2095818975} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &1940938439 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 3.8, y: 0.08, z: 0.95} +--- !u!65 &1940938440 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 2.85, y: 0.08, z: 0.95} +--- !u!65 &1940938441 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 0} +--- !u!65 &1940938442 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1940938437} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0} +--- !u!1 &2054415773 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2054415774} + - component: {fileID: 2054415780} + - component: {fileID: 2054415779} + - component: {fileID: 2054415778} + - component: {fileID: 2054415777} + - component: {fileID: 2054415776} + - component: {fileID: 2054415775} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2054415774 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: -0.26723838, z: 0, w: 0.96363044} + m_LocalPosition: {x: -1.5, y: 0, z: -0.4} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2105131112} + - {fileID: 1331696912} + - {fileID: 1199636760} + - {fileID: 909518691} + - {fileID: 902889316} + - {fileID: 612736414} + m_Father: {fileID: 2095818975} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!65 &2054415775 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 1.9} +--- !u!65 &2054415776 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 1.9} +--- !u!65 &2054415777 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0.95} +--- !u!65 &2054415778 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 1.9, y: 0.08, z: 0} +--- !u!65 &2054415779 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0.95, y: 0.08, z: 0} +--- !u!65 &2054415780 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054415773} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 3 + m_Size: {x: 0.82, y: 0.16, z: 0.82} + m_Center: {x: 0, y: 0.08, z: 0} +--- !u!21 &2058516905 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Demo Dark Platform + m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _BlendModePreserveSpecular: 1 + - _BumpScale: 1 + - _ClearCoatMask: 0 + - _ClearCoatSmoothness: 0 + - _Cull: 2 + - _Cutoff: 0.5 + - _DetailAlbedoMapScale: 1 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _EnvironmentReflections: 1 + - _GlossMapScale: 0 + - _Glossiness: 0 + - _GlossyReflections: 0 + - _Metallic: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.005 + - _QueueOffset: 0 + - _ReceiveShadows: 1 + - _Smoothness: 0.5 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _WorkflowMode: 1 + - _ZWrite: 1 + m_Colors: + - _BaseColor: {r: 0.08, g: 0.09, b: 0.1, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} + m_BuildTextureStacks: [] +--- !u!1 &2095818974 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2095818975} + m_Layer: 0 + m_Name: Perspective Placement Areas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2095818975 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2095818974} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2054415774} + - {fileID: 1940938438} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2105131111 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2105131112} + - component: {fileID: 2105131114} + - component: {fileID: 2105131113} + m_Layer: 0 + m_Name: PerspectiveRotatedLGrid_VisualCell_0_0 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2105131112 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.08, z: 0} + m_LocalScale: {x: 0.78, y: 0.08, z: 0.78} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2054415774} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &2105131113 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 703760602} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 1 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &2105131114 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2105131111} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 750890961} + - {fileID: 1241248866} + - {fileID: 901106304} + - {fileID: 2095818975} + - {fileID: 1808396408} + - {fileID: 1863570095} + - {fileID: 84949471} diff --git a/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta new file mode 100644 index 0000000000..feeb11033b --- /dev/null +++ b/Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1c9d7b93a4ba34245987ea17f02d8f60 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs b/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs index 371f1c9195..0a1f9bbcab 100644 --- a/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs +++ b/Assets/Tests/Editor/RaycastGridAnnotatorTests.cs @@ -137,6 +137,26 @@ public void CreateClusters_WhenSamplesHitDifferentColliders_ShouldReturnClusterP Assert.That(clusters[1].Representative.ClusterKey, Is.EqualTo(2)); } + [Test] + public void CreateClusterKey_WhenGameObjectHasMultipleColliders_ShouldGroupByGameObject() + { + GameObject gameObject = new GameObject("MultiColliderPlacementArea"); + try + { + BoxCollider boxCollider = gameObject.AddComponent(); + SphereCollider sphereCollider = gameObject.AddComponent(); + + int boxClusterKey = RaycastGridAnnotator.CreateClusterKey(boxCollider); + int sphereClusterKey = RaycastGridAnnotator.CreateClusterKey(sphereCollider); + + Assert.That(boxClusterKey, Is.EqualTo(sphereClusterKey)); + } + finally + { + Object.DestroyImmediate(gameObject); + } + } + [Test] public void CreateClusters_WhenSamplesFormLShape_ShouldChooseActualHitClosestToCentroid() { @@ -361,6 +381,137 @@ public void CreatePhysicsColliderElement_WhenClusterHasSingleSample_ShouldUseOne Assert.That(element.BoundsMaxY, Is.EqualTo(210f)); } + [Test] + public void CreateOutlineSegments_WhenSingleCell_ShouldReturnFourEdges() + { + List samples = new List + { + CreateSample(1, 10f, 20f, 1, 1) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 10f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(4)); + AssertSegment(segments[0], 5f, 10f, 15f, 10f); + AssertSegment(segments[1], 5f, 30f, 15f, 30f); + AssertSegment(segments[2], 5f, 10f, 5f, 30f); + AssertSegment(segments[3], 15f, 10f, 15f, 30f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsAreAdjacent_ShouldMergeSharedEdges() + { + List samples = new List + { + CreateSample(1, 10f, 20f, 1, 1), + CreateSample(1, 20f, 20f, 1, 2) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 10f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(4)); + AssertSegment(segments[0], 5f, 10f, 25f, 10f); + AssertSegment(segments[1], 5f, 30f, 25f, 30f); + AssertSegment(segments[2], 5f, 10f, 5f, 30f); + AssertSegment(segments[3], 25f, 10f, 25f, 30f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsFormLShape_ShouldKeepConcaveOutline() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2), + CreateSample(1, 10f, 20f, 2, 1) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(6)); + AssertSegment(segments[0], 5f, 5f, 25f, 5f); + AssertSegment(segments[1], 15f, 15f, 25f, 15f); + AssertSegment(segments[2], 5f, 25f, 15f, 25f); + AssertSegment(segments[3], 5f, 5f, 5f, 25f); + AssertSegment(segments[4], 15f, 15f, 15f, 25f); + AssertSegment(segments[5], 25f, 5f, 25f, 15f); + } + + [Test] + public void CreateOutlineSegments_WhenCellsHaveHole_ShouldReturnInnerAndOuterEdges() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2), + CreateSample(1, 30f, 10f, 1, 3), + CreateSample(1, 10f, 20f, 2, 1), + CreateSample(1, 30f, 20f, 2, 3), + CreateSample(1, 10f, 30f, 3, 1), + CreateSample(1, 20f, 30f, 3, 2), + CreateSample(1, 30f, 30f, 3, 3) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(8)); + Assert.That(ContainsSegment(segments, 15f, 15f, 25f, 15f), Is.True); + Assert.That(ContainsSegment(segments, 15f, 25f, 25f, 25f), Is.True); + Assert.That(ContainsSegment(segments, 15f, 15f, 15f, 25f), Is.True); + Assert.That(ContainsSegment(segments, 25f, 15f, 25f, 25f), Is.True); + } + + [Test] + public void CreateOutlineSegments_WhenCellsAreDisconnected_ShouldKeepSeparateComponents() + { + List samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 30f, 10f, 1, 3) + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + List segments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(samples, coverage); + + Assert.That(segments.Count, Is.EqualTo(8)); + Assert.That(ContainsSegment(segments, 5f, 5f, 15f, 5f), Is.True); + Assert.That(ContainsSegment(segments, 25f, 5f, 35f, 5f), Is.True); + } + + [Test] + public void CreatePhysicsColliderElement_WhenSamplesHaveGridCells_ShouldAttachOutlineSegments() + { + RaycastClusterInfo cluster = new RaycastClusterInfo + { + Representative = CreateSample(1, 10f, 10f, 1, 1), + SampleCount = 2, + Samples = new List + { + CreateSample(1, 10f, 10f, 1, 1), + CreateSample(1, 20f, 10f, 1, 2) + } + }; + RaycastSampleCoverage coverage = CreateCoverage(5f, 5f, 0f, 0f, 100f, 100f); + + UIElementInfo element = RaycastGridAnnotator.CreatePhysicsColliderElement( + "R1", + cluster, + CreateMetadata(), + coverage); + + Assert.That(element.RaycastOutlineSegments.Count, Is.EqualTo(4)); + AssertSegment(element.RaycastOutlineSegments[0], 5f, 5f, 25f, 5f); + } + [Test] public void IsUiOcclusionRaycastResult_WhenGraphicRaycasterHit_ShouldReturnTrue() { @@ -492,16 +643,57 @@ public void CreateLayerSummaries_WhenNoHits_ShouldReturnEmptyList() Assert.That(summaries, Is.Empty); } - private static RaycastClusterSample CreateSample(int clusterKey, float inputX, float inputY) + private static RaycastClusterSample CreateSample( + int clusterKey, + float inputX, + float inputY, + int row = 0, + int column = 0) { return new RaycastClusterSample { ClusterKey = clusterKey, InputX = inputX, - InputY = inputY + InputY = inputY, + Row = row, + Column = column }; } + private static bool ContainsSegment( + List segments, + float startX, + float startY, + float endX, + float endY) + { + foreach (RaycastOutlineSegment segment in segments) + { + if (segment.StartX == startX && + segment.StartY == startY && + segment.EndX == endX && + segment.EndY == endY) + { + return true; + } + } + + return false; + } + + private static void AssertSegment( + RaycastOutlineSegment segment, + float startX, + float startY, + float endX, + float endY) + { + Assert.That(segment.StartX, Is.EqualTo(startX)); + Assert.That(segment.StartY, Is.EqualTo(startY)); + Assert.That(segment.EndX, Is.EqualTo(endX)); + Assert.That(segment.EndY, Is.EqualTo(endY)); + } + private static RaycastGridPointInfo CreateLayerHitPoint( string layer, int layerIndex, diff --git a/Assets/Tests/Editor/UIElementAnnotatorTests.cs b/Assets/Tests/Editor/UIElementAnnotatorTests.cs index 7a3cf20bdc..501bf37ba5 100644 --- a/Assets/Tests/Editor/UIElementAnnotatorTests.cs +++ b/Assets/Tests/Editor/UIElementAnnotatorTests.cs @@ -126,6 +126,40 @@ public void CalculateBorderEdgeRects_WhenBoundsAreProvided_ShouldPlaceEdgesInsid Assert.That(edgeRects.Right, Is.EqualTo(new Rect(108f, 22f, 2f, 46f))); } + [Test] + public void ConvertTopLeftOutlineSegmentToScreenSegment_ShouldFlipYForCanvasSpace() + { + RaycastOutlineSegment inputSegment = new RaycastOutlineSegment(10f, 20f, 30f, 20f); + + RaycastOutlineSegment screenSegment = + UIElementAnnotator.ConvertTopLeftOutlineSegmentToScreenSegment(inputSegment, 100f); + + Assert.That(screenSegment.StartX, Is.EqualTo(10f)); + Assert.That(screenSegment.StartY, Is.EqualTo(80f)); + Assert.That(screenSegment.EndX, Is.EqualTo(30f)); + Assert.That(screenSegment.EndY, Is.EqualTo(80f)); + } + + [Test] + public void CalculateOutlineSegmentRect_WhenHorizontalSegment_ShouldExtendBothEndpoints() + { + RaycastOutlineSegment segment = new RaycastOutlineSegment(10f, 20f, 30f, 20f); + + Rect rect = UIElementAnnotator.CalculateOutlineSegmentRect(segment, 4f); + + Assert.That(rect, Is.EqualTo(new Rect(8f, 18f, 24f, 4f))); + } + + [Test] + public void CalculateOutlineSegmentRect_WhenVerticalSegment_ShouldExtendBothEndpoints() + { + RaycastOutlineSegment segment = new RaycastOutlineSegment(10f, 20f, 10f, 50f); + + Rect rect = UIElementAnnotator.CalculateOutlineSegmentRect(segment, 4f); + + Assert.That(rect, Is.EqualTo(new Rect(8f, 18f, 4f, 34f))); + } + [Test] public void GetAnnotationBorderColors_WhenAnnotationColorIsProvided_ShouldPutAnnotationColorInTheMiddle() { diff --git a/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts b/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts index 0344c3f9de..eea350f698 100644 --- a/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts +++ b/Packages/src/Cli~/src/__tests__/cli-e2e.test.ts @@ -889,22 +889,24 @@ describe('CLI E2E Tests (requires running Unity)', () => { // Domain Reload tests must run last to avoid affecting other tests describe('compile --force-recompile (Domain Reload)', () => { - it('should support --force-recompile option', () => { - const { exitCode } = runCli('compile --force-recompile'); + it('should reject --force-recompile without an explicit value', () => { + const result: { stdout: string; stderr: string; exitCode: number } = runCli( + 'compile --force-recompile', + ); - // Domain Reload causes connection to be lost, so we just verify the command runs - // The exit code may be non-zero due to connection being dropped during reload - expect(typeof exitCode).toBe('number'); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("option '--force-recompile ' argument missing"); }); }); describe('compile --wait-for-domain-reload', () => { - it('should support --wait-for-domain-reload option', () => { - const { exitCode } = runCli('compile --wait-for-domain-reload'); + it('should reject --wait-for-domain-reload without an explicit value', () => { + const result: { stdout: string; stderr: string; exitCode: number } = runCli( + 'compile --wait-for-domain-reload', + ); - // This option is intended to survive domain reload and return once the - // compile result is available again. - expect(typeof exitCode).toBe('number'); + expect(result.exitCode).toBe(1); + expect(result.stderr).toContain("option '--wait-for-domain-reload ' argument missing"); }); }); }); diff --git a/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md index 6e927b236f..0f6846d80d 100644 --- a/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Compile/Skill/SKILL.md @@ -10,15 +10,15 @@ Execute Unity project compilation. ## Usage ```bash -uloop compile [--force-recompile] [--wait-for-domain-reload] +uloop compile [--force-recompile ] [--wait-for-domain-reload ] ``` ## Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `--force-recompile` | boolean | `false` | Force full recompilation (triggers Domain Reload) | -| `--wait-for-domain-reload` | boolean | `false` | Wait until Domain Reload completes before returning | +| `--force-recompile` | boolean value | `false` | Force full recompilation (triggers Domain Reload). Pass `true` or `false`; bare flags are not accepted. | +| `--wait-for-domain-reload` | boolean value | `false` | Wait until Domain Reload completes before returning. Pass `true` or `false`; bare flags are not accepted. | ## Global Options @@ -33,7 +33,7 @@ uloop compile [--force-recompile] [--wait-for-domain-reload] uloop compile # Force full recompilation -uloop compile --force-recompile +uloop compile --force-recompile true # Force recompilation and wait for Domain Reload completion uloop compile --force-recompile true --wait-for-domain-reload true diff --git a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md index dd6fb01735..15c34ac072 100644 --- a/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md +++ b/Packages/src/Editor/Api/McpTools/ExecuteDynamicCode/Skill/references/playmode-automation-powershell.md @@ -203,7 +203,7 @@ Use `get-hierarchy` to explore the UI tree structure, then target the right elem **Step 1**: Get Canvas hierarchy to understand UI structure ```powershell -uloop get-hierarchy --root-path 'Canvas' --max-depth 3 +uloop get-hierarchy --root-path 'Canvas' --max-depth 3 --include-inactive true ``` **Step 2**: Based on the hierarchy JSON, click the desired button diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md index cfea5d46a3..9125b7b471 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/SKILL.md @@ -10,7 +10,7 @@ Take a screenshot of any Unity EditorWindow by name and save as PNG. ## Usage ```bash -uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements] [--annotate-raycast-grid] [--raycast-layer-mask ] [--elements-only] [--output-directory ] +uloop screenshot [--window-name ] [--resolution-scale ] [--match-mode ] [--capture-mode ] [--annotate-elements ] [--annotate-raycast-grid ] [--raycast-layer-mask ] [--elements-only ] [--output-directory ] ``` ## Parameters @@ -22,10 +22,10 @@ uloop screenshot [--window-name ] [--resolution-scale ] [--match-mo | `--match-mode` | enum | `exact` | Window name matching mode: `exact`, `prefix`, or `contains`. Ignored when `--capture-mode rendering`. | | `--capture-mode` | enum | `window` | `window`=capture EditorWindow including toolbar, `rendering`=capture game rendering only (PlayMode required, coordinates match simulate-mouse) | | `--output-directory` | string | `""` | Output directory path for saving screenshots. When empty, uses default path (.uloop/outputs/Screenshots/). Accepts absolute paths. | -| `--annotate-elements` | boolean | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Only works with `--capture-mode rendering` in PlayMode. | -| `--annotate-raycast-grid` | boolean | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | -| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by collider and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | -| `--elements-only` | boolean | `false` | Return only annotation JSON without capturing a screenshot image. Requires `--annotate-elements` or `--annotate-raycast-grid`, and `--capture-mode rendering` in PlayMode. | +| `--annotate-elements` | boolean value | `false` | Annotate interactive UI elements with index labels and interaction hints (A / CLICK, B / DRAG, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering` in PlayMode. | +| `--annotate-raycast-grid` | boolean value | `false` | Annotate 3D physics raycast candidate points (R1, R2, ...). Pass `true` or `false`; bare flags are not accepted. Only works with `--capture-mode rendering`; uses Camera.main and its culling mask. | +| `--raycast-layer-mask` | string | `""` | Comma-separated physics layer names for `--annotate-raycast-grid true`. Hits are limited to layers also visible to Camera.main.cullingMask. When set, dense raycast samples are clustered by clickable GameObject and returned as `Type="PhysicsCollider"` entries in `AnnotatedElements`; multiple colliders on the same GameObject share one entry. Samples blocked by EventSystem `GraphicRaycaster` UI hits are skipped. | +| `--elements-only` | boolean value | `false` | Return only annotation JSON without capturing a screenshot image. Pass `true` or `false`; bare flags are not accepted. Requires `--annotate-elements true` or `--annotate-raycast-grid true`, and `--capture-mode rendering` in PlayMode. | ## Match Modes @@ -55,23 +55,20 @@ uloop screenshot uloop screenshot --capture-mode rendering # Annotate interactive UI elements with index labels (for simulate-mouse workflow) -uloop screenshot --capture-mode rendering --annotate-elements +uloop screenshot --capture-mode rendering --annotate-elements true # Annotate 3D physics raycast candidate points -uloop screenshot --capture-mode rendering --annotate-raycast-grid +uloop screenshot --capture-mode rendering --annotate-raycast-grid true # Inspect hit layers, then rerun with the layer used by game input -uloop screenshot --capture-mode rendering --annotate-raycast-grid --elements-only -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Default --elements-only +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --elements-only true +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --elements-only true # Annotate clustered 3D collider candidates on selected layers -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Ground,Clickable +uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Ground,Clickable # Get UI element coordinates without capturing an image (fastest) -uloop screenshot --capture-mode rendering --annotate-elements --elements-only - -# Get clustered 3D collider coordinates without capturing an image -uloop screenshot --capture-mode rendering --annotate-raycast-grid --raycast-layer-mask Default --elements-only +uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true # Take a screenshot of Scene View uloop screenshot --window-name Scene @@ -97,13 +94,13 @@ Returns JSON with: - `Height`: Captured image height in pixels - `ImageCoordinateSystem`: `"top-left-game-view"` for `--capture-mode rendering`, or `"top-left-window"` for window captures - `ResolutionScale`: Resolution scale used for capture - - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates - - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools - - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates - - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` - - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid --raycast-layer-mask ` is used. - - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid` is used without `--raycast-layer-mask`. - - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid` is used without `--raycast-layer-mask`. Empty when a mask is provided. + - `ImageToInputOffsetY`: Y offset added after unscaling image pixels to get mouse-input coordinates (rendering captures only) + - `GameViewWidth` / `GameViewHeight`: Game View size used by mouse input tools (rendering captures only) + - `ScreenshotToInputFormula`: Formula for turning image coordinates into mouse-input coordinates (rendering captures only; unavailable for window captures) + - `UnityInputFormula`: Formula used internally by mouse input tools to inject `Mouse.current.position` (rendering captures only; empty for window captures) + - `AnnotatedElements`: Array of annotated UI element metadata. Also includes clustered `PhysicsCollider` entries when `--annotate-raycast-grid true --raycast-layer-mask ` is used. + - `RaycastGridPoints`: Array of coarse 3D raycast candidate metadata. Empty unless `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. + - `RaycastLayerSummaries`: Dense raycast hit counts by layer when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. Empty when a mask is provided. For `AnnotatedElements` fields and Game View coordinates, read [references/annotated-elements.md](references/annotated-elements.md) before using screenshot coordinates with mouse simulation tools. @@ -114,11 +111,12 @@ When multiple windows match (e.g., multiple Inspector windows or when using `con - Use `uloop focus-window` first if needed - Target window must be open in Unity Editor - Window name matching is always case-insensitive +- Boolean options use value syntax. Write `--annotate-elements true`, `--annotate-raycast-grid true`, and `--elements-only true`; do not use bare boolean flags. - Use `--capture-mode rendering` for coordinates that should be passed to `simulate-mouse-input`, `simulate-mouse-ui`, or `raycast`. - Use `ScreenshotToInputFormula` before passing raw image pixels to mouse tools. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates. -- To discover a useful physics layer, first run `--annotate-raycast-grid` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. +- To discover a useful physics layer, first run `--annotate-raycast-grid true` without `--raycast-layer-mask`, inspect `RaycastLayerSummaries`, then rerun with `--raycast-layer-mask `. - Use `--raycast-layer-mask` with layer names from `find-game-objects` or project code when the game input code raycasts only specific layers. Layers hidden by Camera.main.cullingMask are not reported because they are not visible to the screenshot camera. -- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. `BoundsMinX/Y` and `BoundsMaxX/Y` show the axis-aligned sampled-cell coverage bbox from reachable hits; use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. +- Clustered `PhysicsCollider` entries avoid points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element, including world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. The screenshot overlay draws the reachable sampled-cell outline, while `BoundsMinX/Y` and `BoundsMaxX/Y` remain the axis-aligned sampled-cell bbox for JSON consumers. Use `SimX/SimY` for the actual click point. If every sampled hit in the cluster is covered, that collider is omitted. ### Raycast Annotation Demo Scene @@ -128,5 +126,7 @@ Use `Assets/Scenes/RaycastAnnotationDemoScene.unity` as the maintained visual fi uloop screenshot --capture-mode rendering --annotate-raycast-grid true --raycast-layer-mask Default --resolution-scale 1 ``` -The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile frames should shrink around the blocker, frames should wrap the reachable sampled-cell bbox, and `SimX/SimY` should stay inside each frame. +The scene contains a deterministic 4x4 set of `BoxCollider` tiles and a center `GraphicRaycaster` UI blocker. Verify relative behavior rather than fixed pixel coordinates: center tile outlines should shrink around the blocker, outlines should follow reachable sampled cells, and `SimX/SimY` should stay inside `BoundsMinX/Y` and `BoundsMaxX/Y`. + +Use `Assets/Scenes/RaycastAnnotationPerspectiveDemoScene.unity` as the perspective visual fixture. It contains rotated multi-collider placement areas, separated cell groups, a visual foreground blocker, and a center `GraphicRaycaster` UI blocker. Enter PlayMode, then run the same command above. Verify the outline follows the visible angled placement areas without reverting to a large axis-aligned rectangle. - Do not use `window` captures as mouse-input coordinates because they include Unity Editor chrome. diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md index da1083ff51..11117162d7 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md +++ b/Packages/src/Editor/Api/McpTools/Screenshot/Skill/references/annotated-elements.md @@ -1,10 +1,10 @@ # Annotated Elements and Coordinates -Read this when using `uloop screenshot --capture-mode rendering --annotate-elements` or clustered `--annotate-raycast-grid --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. +Read this when using `uloop screenshot --capture-mode rendering --annotate-elements true` or clustered `--annotate-raycast-grid true --raycast-layer-mask ` output to find coordinates for `simulate-mouse-ui` or `simulate-mouse-input`. ## AnnotatedElements Fields -`AnnotatedElements` is empty unless `--annotate-elements` is used, or unless `--annotate-raycast-grid --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: +`AnnotatedElements` is empty unless `--annotate-elements true` is used, or unless `--annotate-raycast-grid true --raycast-layer-mask ` adds clustered 3D collider candidates. UI entries are sorted by z-order, frontmost first. Each item contains: - `Label`: Index label in JSON (`A` = frontmost, `B` = next, ...). Screenshot labels also include the interaction hint, such as `A / CLICK` or `B / DRAG`. - `Name`: Element name @@ -20,7 +20,7 @@ Read this when using `uloop screenshot --capture-mode rendering --annotate-eleme ## RaycastLayerSummaries Fields -`RaycastLayerSummaries` is populated when `--annotate-raycast-grid` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. +`RaycastLayerSummaries` is populated when `--annotate-raycast-grid true` is used without `--raycast-layer-mask`. It is built from dense raycast samples, while `RaycastGridPoints` remains the coarse 5x5 annotated grid. - `Layer`: Physics layer name to pass to `--raycast-layer-mask` - `LayerIndex`: Unity physics layer index @@ -38,15 +38,15 @@ input_x = image_x / resolutionScale input_y = image_y / resolutionScale + imageToInputOffsetY ``` -When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0`, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are always returned as mouse-input coordinates, so pass those values directly. +When `ResolutionScale` is `1.0` and `ImageToInputOffsetY` is `0` for rendering captures, raw image pixel coordinates already match mouse-input coordinates. `AnnotatedElements[].SimX/SimY` and `RaycastGridPoints[].InputX/InputY` are already mouse-input coordinates in that mode, so pass those values directly. For `PhysicsCollider` entries, `SimX/SimY` is a real sampled raycast hit nearest to the reachable cluster centroid. This avoids synthetic center points that may fall into empty space for L-shaped or ring-shaped collider coverage. Always use `SimX/SimY` for clicking; use `BoundsMinX/Y` and `BoundsMaxX/Y` only as a sampled coverage guide. `--raycast-layer-mask` filters by the requested physics layers and Camera.main.cullingMask. A layer that is requested but hidden from the active camera is treated as not visible and will not produce `PhysicsCollider` entries. -For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds and `SimX/SimY` are both derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. +For clustered `PhysicsCollider` entries, points where the frontmost EventSystem hit comes from a `GraphicRaycaster` UI element are treated as covered by UI. This includes world-space Canvas UI. PhysicsRaycaster and other non-uGUI hits are not treated as UI occlusion. Bounds, screenshot outlines, and `SimX/SimY` are derived from the remaining reachable samples; if every sampled hit in that collider cluster is covered, the collider is omitted from `AnnotatedElements`. -`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. This makes the frame approximate the covered raycast cells instead of shrinking to the sample centers. It may extend up to half a sample step past the visible collider edge, and it still does not guarantee that every interior point is clickable. +`PhysicsCollider` bounds expand each reachable sample by half the dense raycast sampling step in X and Y, then clamp the result to the captured Game View area. The screenshot overlay draws only the outer edges of those reachable sample cells, so angled, L-shaped, separated, and partially UI-covered hit regions do not become one large rectangle. `BoundsMinX/Y` and `BoundsMaxX/Y` are still an axis-aligned bbox for JSON consumers, may extend up to half a sample step past the visible collider edge, and do not guarantee that every interior point is clickable. The mouse input tools convert internally to Unity Input System coordinates: diff --git a/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs b/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs index 1127409204..483379aab4 100644 --- a/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs +++ b/Packages/src/Editor/Api/McpTools/Screenshot/UIElementInfo.cs @@ -19,5 +19,8 @@ public class UIElementInfo public string Label { get; set; } = ""; public int SortingOrder { get; set; } public int SiblingIndex { get; set; } + + internal List RaycastOutlineSegments { get; set; } = + new List(); } } diff --git a/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md b/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md index 451178c995..95f6185a18 100644 --- a/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md +++ b/Packages/src/Editor/Api/McpTools/SimulateMouseUi/Skill/SKILL.md @@ -11,10 +11,10 @@ Simulate mouse interaction on Unity PlayMode UI: $ARGUMENTS ## Workflow 1. Ensure Unity is in PlayMode (use `uloop control-play-mode --action Play` if not) -2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements --elements-only` +2. Get UI element info: `uloop screenshot --capture-mode rendering --annotate-elements true --elements-only true` 3. Use the `AnnotatedElements` array to find the target element by `Label`, `Name`, or `Path` (A=frontmost, B=next, ...). Use `Interaction` to distinguish click targets from drag/drop/text targets, then use `SimX`/`SimY` directly as `--x`/`--y` coordinates. 4. Execute the appropriate `uloop simulate-mouse-ui` command -5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements` +5. Take a screenshot to verify the result: `uloop screenshot --capture-mode rendering --annotate-elements true` 6. Report what happened ## Tool Reference @@ -127,6 +127,6 @@ Returns JSON with: - `EndPositionX`: Drag end X coordinate (nullable float; populated for drag actions only) - `EndPositionY`: Drag end Y coordinate (nullable float; populated for drag actions only) -These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements`. +These are the only eight fields. There is no `Button`, `Duration`, `DragSpeed`, raycast list, or pointer-event log in the response — verify the visual outcome with a follow-up `uloop screenshot --capture-mode rendering --annotate-elements true`. Note: Click and LongPress on empty space (no UI element) still return `Success = true` with `HitGameObjectName = null`. Drag actions on empty space return `Success = false`. diff --git a/Packages/src/Editor/Utils/RaycastGridAnnotator.cs b/Packages/src/Editor/Utils/RaycastGridAnnotator.cs index 40ce38cfd5..8a2065957e 100644 --- a/Packages/src/Editor/Utils/RaycastGridAnnotator.cs +++ b/Packages/src/Editor/Utils/RaycastGridAnnotator.cs @@ -13,8 +13,8 @@ internal static class RaycastGridAnnotator { private const int GRID_COLUMNS = 5; private const int GRID_ROWS = 5; - private const int CLUSTERED_GRID_COLUMNS = 20; - private const int CLUSTERED_GRID_ROWS = 20; + private const int CLUSTERED_GRID_COLUMNS = 40; + private const int CLUSTERED_GRID_ROWS = 40; private const float MARKER_SIZE = 18f; internal static List CollectRaycastGridPoints( @@ -353,7 +353,7 @@ private static RaycastClusterCollection CollectClusterSamples( RaycastHit hit = raycastResult.Hits[0]; Collider collider = hit.collider; - int clusterKey = collider.GetInstanceID(); + int clusterKey = CreateClusterKey(collider); if (!clusterCollection.MetadataByClusterKey.ContainsKey(clusterKey)) { clusterCollection.MetadataByClusterKey.Add( @@ -361,22 +361,34 @@ private static RaycastClusterCollection CollectClusterSamples( CreateColliderMetadata(collider)); } - clusterCollection.Samples.Add(CreateClusterSample(inputPosition, clusterKey)); + clusterCollection.Samples.Add(CreateClusterSample(inputPosition, clusterKey, row, column)); } } return clusterCollection; } + internal static int CreateClusterKey(Collider collider) + { + Debug.Assert(collider != null, "Collider is required for raycast clustering."); + + // A placement area can use several colliders on one object; one annotation should describe the clickable object. + return collider!.gameObject.GetInstanceID(); + } + private static RaycastClusterSample CreateClusterSample( Vector2 inputPosition, - int clusterKey) + int clusterKey, + int row, + int column) { return new RaycastClusterSample { ClusterKey = clusterKey, InputX = inputPosition.x, - InputY = inputPosition.y + InputY = inputPosition.y, + Row = row, + Column = column }; } @@ -401,6 +413,8 @@ internal static UIElementInfo CreatePhysicsColliderElement( Debug.Assert(cluster.Samples.Count > 0, "Physics collider cluster must contain sampled hits."); RaycastClusterSample representative = cluster.Representative; RaycastSampleBounds sampleBounds = CalculateSampleCellBounds(cluster.Samples, sampleCoverage); + List outlineSegments = + RaycastSampleOutlineBuilder.CreateOutlineSegments(cluster.Samples, sampleCoverage); UIElementInfo element = new UIElementInfo { @@ -418,7 +432,8 @@ internal static UIElementInfo CreatePhysicsColliderElement( SortingOrder = 0, SiblingIndex = 0, Layer = metadata.Layer, - Components = new List(metadata.Components) + Components = new List(metadata.Components), + RaycastOutlineSegments = outlineSegments }; Debug.Assert( diff --git a/Packages/src/Editor/Utils/RaycastHitClusterer.cs b/Packages/src/Editor/Utils/RaycastHitClusterer.cs index 0116c7f51f..9f1b10c09f 100644 --- a/Packages/src/Editor/Utils/RaycastHitClusterer.cs +++ b/Packages/src/Editor/Utils/RaycastHitClusterer.cs @@ -150,6 +150,8 @@ internal sealed class RaycastClusterSample public int ClusterKey { get; set; } public float InputX { get; set; } public float InputY { get; set; } + public int Row { get; set; } + public int Column { get; set; } } /// diff --git a/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs new file mode 100644 index 0000000000..134c8b2a49 --- /dev/null +++ b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs @@ -0,0 +1,283 @@ +#nullable enable +using System.Collections.Generic; +using UnityEngine; + +namespace io.github.hatayama.uLoopMCP +{ + /// + /// Builds screen-space outlines from sampled raycast hit cells. + /// + internal static class RaycastSampleOutlineBuilder + { + internal static List CreateOutlineSegments( + List samples, + RaycastSampleCoverage sampleCoverage) + { + Debug.Assert(samples != null, "Raycast samples must not be null."); + List validSamples = samples!; + HashSet occupiedCells = CreateOccupiedCells(validSamples); + List segments = new List(); + + foreach (RaycastClusterSample sample in validSamples) + { + if (!HasGridCell(sample)) + { + continue; + } + + RaycastSampleCellBounds cellBounds = CalculateCellBounds(sample, sampleCoverage); + AddBoundarySegments(sample, cellBounds, occupiedCells, segments); + } + + return MergeCollinearSegments(segments); + } + + private static HashSet CreateOccupiedCells(List samples) + { + HashSet occupiedCells = new HashSet(); + foreach (RaycastClusterSample sample in samples) + { + if (!HasGridCell(sample)) + { + continue; + } + + occupiedCells.Add(new RaycastSampleCellKey(sample.Row, sample.Column)); + } + + return occupiedCells; + } + + private static bool HasGridCell(RaycastClusterSample sample) + { + return sample.Row > 0 && sample.Column > 0; + } + + private static void AddBoundarySegments( + RaycastClusterSample sample, + RaycastSampleCellBounds cellBounds, + HashSet occupiedCells, + List segments) + { + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row - 1, sample.Column))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MinY, + cellBounds.MaxX, + cellBounds.MinY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row + 1, sample.Column))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MaxY, + cellBounds.MaxX, + cellBounds.MaxY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row, sample.Column - 1))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MinX, + cellBounds.MinY, + cellBounds.MinX, + cellBounds.MaxY)); + } + + if (!occupiedCells.Contains(new RaycastSampleCellKey(sample.Row, sample.Column + 1))) + { + segments.Add(new RaycastOutlineSegment( + cellBounds.MaxX, + cellBounds.MinY, + cellBounds.MaxX, + cellBounds.MaxY)); + } + } + + private static RaycastSampleCellBounds CalculateCellBounds( + RaycastClusterSample sample, + RaycastSampleCoverage sampleCoverage) + { + return new RaycastSampleCellBounds( + Mathf.Clamp(sample.InputX - sampleCoverage.HalfStepX, sampleCoverage.MinX, sampleCoverage.MaxX), + Mathf.Clamp(sample.InputY - sampleCoverage.HalfStepY, sampleCoverage.MinY, sampleCoverage.MaxY), + Mathf.Clamp(sample.InputX + sampleCoverage.HalfStepX, sampleCoverage.MinX, sampleCoverage.MaxX), + Mathf.Clamp(sample.InputY + sampleCoverage.HalfStepY, sampleCoverage.MinY, sampleCoverage.MaxY)); + } + + private static List MergeCollinearSegments(List segments) + { + segments.Sort(CompareSegments); + List mergedSegments = new List(); + + foreach (RaycastOutlineSegment segment in segments) + { + if (mergedSegments.Count == 0) + { + mergedSegments.Add(segment); + continue; + } + + int lastIndex = mergedSegments.Count - 1; + RaycastOutlineSegment lastSegment = mergedSegments[lastIndex]; + if (!CanMerge(lastSegment, segment)) + { + mergedSegments.Add(segment); + continue; + } + + mergedSegments[lastIndex] = MergeSegments(lastSegment, segment); + } + + return mergedSegments; + } + + private static int CompareSegments(RaycastOutlineSegment left, RaycastOutlineSegment right) + { + int orientationComparison = GetOrientationIndex(left).CompareTo(GetOrientationIndex(right)); + if (orientationComparison != 0) + { + return orientationComparison; + } + + if (IsHorizontal(left)) + { + int yComparison = left.StartY.CompareTo(right.StartY); + if (yComparison != 0) + { + return yComparison; + } + + return left.StartX.CompareTo(right.StartX); + } + + int xComparison = left.StartX.CompareTo(right.StartX); + if (xComparison != 0) + { + return xComparison; + } + + return left.StartY.CompareTo(right.StartY); + } + + private static bool CanMerge(RaycastOutlineSegment left, RaycastOutlineSegment right) + { + if (IsHorizontal(left) && IsHorizontal(right)) + { + return Approximately(left.StartY, right.StartY) && + Approximately(left.EndY, right.EndY) && + Approximately(left.EndX, right.StartX); + } + + if (IsVertical(left) && IsVertical(right)) + { + return Approximately(left.StartX, right.StartX) && + Approximately(left.EndX, right.EndX) && + Approximately(left.EndY, right.StartY); + } + + return false; + } + + private static RaycastOutlineSegment MergeSegments( + RaycastOutlineSegment left, + RaycastOutlineSegment right) + { + if (IsHorizontal(left)) + { + return new RaycastOutlineSegment(left.StartX, left.StartY, right.EndX, right.EndY); + } + + return new RaycastOutlineSegment(left.StartX, left.StartY, right.EndX, right.EndY); + } + + private static int GetOrientationIndex(RaycastOutlineSegment segment) + { + if (IsHorizontal(segment)) + { + return 0; + } + + return 1; + } + + private static bool IsHorizontal(RaycastOutlineSegment segment) + { + return Approximately(segment.StartY, segment.EndY); + } + + private static bool IsVertical(RaycastOutlineSegment segment) + { + return Approximately(segment.StartX, segment.EndX); + } + + private static bool Approximately(float left, float right) + { + return Mathf.Abs(left - right) <= 0.001f; + } + + private readonly struct RaycastSampleCellKey + { + private readonly int _row; + private readonly int _column; + + public RaycastSampleCellKey(int row, int column) + { + _row = row; + _column = column; + } + + public override bool Equals(object obj) + { + if (!(obj is RaycastSampleCellKey other)) + { + return false; + } + + return _row == other._row && _column == other._column; + } + + public override int GetHashCode() + { + return (_row * 397) ^ _column; + } + } + + private readonly struct RaycastSampleCellBounds + { + public readonly float MinX; + public readonly float MinY; + public readonly float MaxX; + public readonly float MaxY; + + public RaycastSampleCellBounds(float minX, float minY, float maxX, float maxY) + { + MinX = minX; + MinY = minY; + MaxX = maxX; + MaxY = maxY; + } + } + } + + /// + /// Represents one axis-aligned outline segment in top-left Game View input coordinates. + /// + internal readonly struct RaycastOutlineSegment + { + public readonly float StartX; + public readonly float StartY; + public readonly float EndX; + public readonly float EndY; + + public RaycastOutlineSegment(float startX, float startY, float endX, float endY) + { + StartX = startX; + StartY = startY; + EndX = endX; + EndY = endY; + } + } +} diff --git a/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta new file mode 100644 index 0000000000..c914aecd5d --- /dev/null +++ b/Packages/src/Editor/Utils/RaycastSampleOutlineBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 227bc5572603b47b9b72c96096c285bf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/Utils/UIElementAnnotator.cs b/Packages/src/Editor/Utils/UIElementAnnotator.cs index 00a98bddbf..794df6eccb 100644 --- a/Packages/src/Editor/Utils/UIElementAnnotator.cs +++ b/Packages/src/Editor/Utils/UIElementAnnotator.cs @@ -114,10 +114,11 @@ public static GameObject CreateAnnotationOverlay(List elements, f Font font = Resources.GetBuiltinResource("LegacyRuntime.ttf"); List drawInfos = new List(elements.Count); + float physicsAnnotationScreenHeight = CalculatePhysicsAnnotationScreenHeight(elements); foreach (UIElementInfo element in elements) { - drawInfos.Add(CreateAnnotationDrawInfo(element)); + drawInfos.Add(CreateAnnotationDrawInfo(element, physicsAnnotationScreenHeight)); } foreach (AnnotationDrawInfo drawInfo in drawInfos) @@ -373,6 +374,12 @@ private static void CreateAnnotationBorderForElement( AnnotationDrawInfo drawInfo, AnnotationBorderMetrics borderMetrics) { + if (drawInfo.OutlineSegments.Count > 0) + { + CreateAnnotationOutlineForElement(parent, drawInfo, borderMetrics); + return; + } + CreateBorder( parent, "LightOuter", @@ -402,6 +409,32 @@ private static void CreateAnnotationBorderForElement( drawInfo.BorderColors.Inner); } + private static void CreateAnnotationOutlineForElement( + Transform parent, + AnnotationDrawInfo drawInfo, + AnnotationBorderMetrics borderMetrics) + { + float outerThickness = borderMetrics.ColorThickness + borderMetrics.NeutralThickness * 2f; + CreateOutline( + parent, + "LightOuter", + drawInfo.OutlineSegments, + outerThickness, + drawInfo.BorderColors.Outer); + CreateOutline( + parent, + "ColorMiddle", + drawInfo.OutlineSegments, + borderMetrics.ColorThickness, + drawInfo.BorderColors.Middle); + CreateOutline( + parent, + "DarkInner", + drawInfo.OutlineSegments, + borderMetrics.NeutralThickness, + drawInfo.BorderColors.Inner); + } + private static void CreateAnnotationLabelForElement( Transform parent, AnnotationDrawInfo drawInfo, @@ -419,22 +452,86 @@ private static void CreateAnnotationLabelForElement( borderMetrics.LabelOutlineDistance); } - private static AnnotationDrawInfo CreateAnnotationDrawInfo(UIElementInfo element) + private static AnnotationDrawInfo CreateAnnotationDrawInfo( + UIElementInfo element, + float physicsAnnotationScreenHeight) { Color color = GetAnnotationColorForElement(element); Color contrastColor = GetContrastingTextColor(color); AnnotationBorderColors borderColors = GetAnnotationBorderColors(color); string displayLabel = CreateDisplayLabel(element); + float screenMinX = element.BoundsMinX; + float screenMinY = element.BoundsMinY; + float screenMaxX = element.BoundsMaxX; + float screenMaxY = element.BoundsMaxY; + List outlineSegments = element.RaycastOutlineSegments; + + if (IsPhysicsColliderElement(element)) + { + Debug.Assert( + physicsAnnotationScreenHeight >= 0f, + "Physics collider annotations require a non-negative Game View height."); + screenMinY = physicsAnnotationScreenHeight - element.BoundsMaxY; + screenMaxY = physicsAnnotationScreenHeight - element.BoundsMinY; + outlineSegments = ConvertTopLeftOutlineSegmentsToScreenSegments( + element.RaycastOutlineSegments, + physicsAnnotationScreenHeight); + } return new AnnotationDrawInfo( - element.BoundsMinX, - element.BoundsMinY, - element.BoundsMaxX, - element.BoundsMaxY, + screenMinX, + screenMinY, + screenMaxX, + screenMaxY, color, contrastColor, borderColors, - displayLabel); + displayLabel, + outlineSegments); + } + + private static float CalculatePhysicsAnnotationScreenHeight(List elements) + { + foreach (UIElementInfo element in elements) + { + if (!IsPhysicsColliderElement(element)) + { + continue; + } + + return GameViewCoordinateUtility.GetMainGameViewSize().y; + } + + return 0f; + } + + private static bool IsPhysicsColliderElement(UIElementInfo element) + { + return element.Type == "PhysicsCollider"; + } + + private static List ConvertTopLeftOutlineSegmentsToScreenSegments( + List outlineSegments, + float screenHeight) + { + List screenSegments = new List(outlineSegments.Count); + foreach (RaycastOutlineSegment segment in outlineSegments) + { + screenSegments.Add(ConvertTopLeftOutlineSegmentToScreenSegment(segment, screenHeight)); + } + + return screenSegments; + } + + internal static RaycastOutlineSegment ConvertTopLeftOutlineSegmentToScreenSegment( + RaycastOutlineSegment segment, + float screenHeight) + { + return new RaycastOutlineSegment( + segment.StartX, + screenHeight - segment.StartY, + segment.EndX, + screenHeight - segment.EndY); } private static void CreateBorder( @@ -450,6 +547,39 @@ private static void CreateBorder( CreateBorderEdge(parent, $"{name}_Right", borderEdgeRects.Right, color); } + private static void CreateOutline( + Transform parent, + string name, + List outlineSegments, + float thickness, + Color color) + { + for (int i = 0; i < outlineSegments.Count; i++) + { + Rect rect = CalculateOutlineSegmentRect(outlineSegments[i], thickness); + CreateBorderEdge(parent, $"{name}_Outline_{i}", rect, color); + } + } + + internal static Rect CalculateOutlineSegmentRect(RaycastOutlineSegment segment, float thickness) + { + Debug.Assert(thickness >= 0f, "Outline thickness must not be negative."); + bool horizontal = Mathf.Approximately(segment.StartY, segment.EndY); + bool vertical = Mathf.Approximately(segment.StartX, segment.EndX); + Debug.Assert(horizontal || vertical, "Raycast outline segments must be axis-aligned."); + + if (horizontal) + { + float minX = Mathf.Min(segment.StartX, segment.EndX) - thickness / 2f; + float width = Mathf.Abs(segment.EndX - segment.StartX) + thickness; + return new Rect(minX, segment.StartY - thickness / 2f, width, thickness); + } + + float minY = Mathf.Min(segment.StartY, segment.EndY) - thickness / 2f; + float height = Mathf.Abs(segment.EndY - segment.StartY) + thickness; + return new Rect(segment.StartX - thickness / 2f, minY, thickness, height); + } + internal static BorderEdgeRects CalculateBorderEdgeRects( float minX, float minY, float maxX, float maxY, float thickness) { @@ -701,6 +831,7 @@ private readonly struct AnnotationDrawInfo public readonly Color ContrastColor; public readonly AnnotationBorderColors BorderColors; public readonly string DisplayLabel; + public readonly List OutlineSegments; public AnnotationDrawInfo( float screenMinX, @@ -710,7 +841,8 @@ public AnnotationDrawInfo( Color color, Color contrastColor, AnnotationBorderColors borderColors, - string displayLabel) + string displayLabel, + List outlineSegments) { ScreenMinX = screenMinX; ScreenMinY = screenMinY; @@ -720,6 +852,7 @@ public AnnotationDrawInfo( ContrastColor = contrastColor; BorderColors = borderColors; DisplayLabel = displayLabel; + OutlineSegments = outlineSegments; } }