diff --git a/.agents/skills/uloop-clear-console/SKILL.md b/.agents/skills/uloop-clear-console/SKILL.md index 8f34706efa..723a1b526a 100644 --- a/.agents/skills/uloop-clear-console/SKILL.md +++ b/.agents/skills/uloop-clear-console/SKILL.md @@ -20,22 +20,6 @@ uloop clear-console [--add-confirmation-message] |-----------|------|---------|-------------| | `--add-confirmation-message` | flag | - | Add confirmation message after clearing | -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Clear console -uloop clear-console - -# Clear with confirmation -uloop clear-console --add-confirmation-message -``` - ## Output Returns JSON with: diff --git a/.agents/skills/uloop-compile/SKILL.md b/.agents/skills/uloop-compile/SKILL.md index 3eeb6bfe4e..2a95ecfc63 100644 --- a/.agents/skills/uloop-compile/SKILL.md +++ b/.agents/skills/uloop-compile/SKILL.md @@ -22,25 +22,6 @@ uloop compile [--force-recompile] [--no-wait-for-domain-reload] [--stop-on-exter | `--no-wait-for-domain-reload` | flag | - | Return before Domain Reload completion | | `--stop-on-external-scene-changes` | flag | - | Stop before compilation if open Scene files changed externally instead of auto-reloading them | -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Check compilation -uloop compile - -# Start compilation without waiting for Domain Reload completion -uloop compile --no-wait-for-domain-reload - -# Stop instead of auto-reloading externally changed open Scene files -uloop compile --stop-on-external-scene-changes -``` - ## Output Returns JSON: diff --git a/.agents/skills/uloop-control-play-mode/SKILL.md b/.agents/skills/uloop-control-play-mode/SKILL.md index fc85b7df71..3bb8b5f165 100644 --- a/.agents/skills/uloop-control-play-mode/SKILL.md +++ b/.agents/skills/uloop-control-play-mode/SKILL.md @@ -21,31 +21,6 @@ uloop control-play-mode [options] | `--action` | string | `Play` | Action to perform: `Play`, `Stop`, `Pause`, `Step` | | `--timeout-seconds` | integer | `180` | Maximum seconds to wait for the requested play mode state | -## Global Options - -| Option | Description | -|--------|-------------| -| `--project-path ` | Optional. Use only when the target Unity project is not the current directory. | - -## Examples - -```bash -# Start play mode -uloop control-play-mode --action Play - -# Start play mode with a longer wait budget -uloop control-play-mode --action Play --timeout-seconds 600 - -# Stop play mode -uloop control-play-mode --action Stop - -# Pause play mode -uloop control-play-mode --action Pause - -# Advance exactly one frame while paused (Next Frame button) -uloop control-play-mode --action Step -``` - ## Output Returns JSON with the current play mode state: @@ -59,5 +34,5 @@ Returns JSON with the current play mode state: ## Notes - Stop on an already-stopped Editor sets `Changed: false`, `WasAlreadyStopped: true` -- Step is independent of `Time.timeScale`; requires PlayMode to be running +- `Step` advances exactly one frame and leaves PlayMode paused (the Editor's Next Frame button); it is independent of `Time.timeScale` and requires PlayMode to be running - The command waits for the requested state before returning. Increase `--timeout-seconds` for projects with slow PlayMode entry. diff --git a/.agents/skills/uloop-execute-dynamic-code/SKILL.md b/.agents/skills/uloop-execute-dynamic-code/SKILL.md index c7b176a17c..e5be89c998 100644 --- a/.agents/skills/uloop-execute-dynamic-code/SKILL.md +++ b/.agents/skills/uloop-execute-dynamic-code/SKILL.md @@ -13,24 +13,16 @@ For basic selected GameObject discovery or property inspection, use `find-game-o This tool can inspect reachable Unity state, such as GameObjects, components, public properties, static values, and method results. It cannot directly read local variables or intermediate calculations inside an already-running method. When those values matter, enable a source pause point on that line instead (`uloop enable-pause-point --file --line `, see the `uloop-wait-for-pause-point` skill): the hit response's `CapturedVariables` already contains the locals, parameters, and instance fields at that line, with no code edit or recompile. Do not try to reconstruct those values with execute-dynamic-code. -## Workflow - -1. Read the relevant reference file(s) from the Code Examples section below -2. Construct C# code based on the reference examples -3. Execute with `--code` or `--code-file` using the active shell's quoting guidance -4. Report the execution result - ## Parameters - `--code ''`: Inline C# statements to execute. Use direct statements only; `return` is optional, and `using` directives may appear at the top of the snippet. - `--code-file `: Read the C# statements from a file instead of `--code`. Use this when the active shell or launcher cannot preserve inline code exactly. Exactly one of `--code` or `--code-file` is required; combining them is an error. -- **Shell-specific quoting**: Read [references/playmode-automation-powershell.md](references/playmode-automation-powershell.md) for Windows/PowerShell multiline commands and [references/playmode-automation-zsh.md](references/playmode-automation-zsh.md) for zsh/macOS examples. - `--parameters {}` (advanced, optional): Pass a shell-quoted JSON object literal when reusing a snippet with varying data or when keeping values outside the code. Values are exposed as `parameters["param0"]`, `parameters["param1"]`, and so on. Omit this flag for most snippets. Do not pass a JSON string value such as `"{\"param0\":\"value\"}"`. - `--wait-for-domain-reload` (optional): Wait for Domain Reload recovery after snippets that intentionally trigger Unity script reload or import work. Omit this for normal inspection and editor-state workflows. ## Code Rules -Write direct statements only — no class/namespace/method wrappers. Return is optional. +Write direct statements from your own Unity API knowledge — no class/namespace/method wrappers. Return is optional. ```csharp using UnityEngine; @@ -40,6 +32,26 @@ return x; Prefer terminal commands for file operations and keep snippets focused on Unity Editor state that existing uloop tools cannot inspect or change. +## Shell Quoting + +- zsh/bash: single-quote the whole snippet so C# double quotes pass through unchanged: `--code 'return "hi";'`. For a single quote inside the snippet, close and reopen the shell string with `'\''`. +- PowerShell 7 (`pwsh`): for multiline snippets, assign a single-quoted here-string (`$code = @'` ... `'@`) and pass `--code $code`. Inline, single-quoted arguments preserve C# double quotes; double an inner single quote (`''`). +- Windows PowerShell 5.1 removes unescaped double quotes from native command arguments: escape them as `\"`, or prefer `--code-file`. +- Pass `--parameters` as a single-quoted JSON object literal in both shells, for example `--parameters '{"param0":"value"}'`. +- On Windows, multiline `--code` requires the native `uloop.exe`. If `(Get-Command uloop).Source` resolves to a legacy `.cmd` shim, run `uloop install` and open a new terminal. +- If quoting still mangles the snippet, switch to `--code-file`. + +## When To Use Input Simulation Tools Instead + +Calling UI handlers or runtime methods directly from a snippet is the better choice for targeted automation, direct state control, or quick diagnostics. Switch to the dedicated input tools only when the input route itself is part of what you need to verify: + +| Scenario | Recommended tool | Why | +|----------|------------------|-----| +| Verify that a uGUI element responds through the real EventSystem pointer path | `simulate-mouse-ui` | Fires `PointerDown` / `PointerUp` / `PointerClick` / drag events through EventSystem raycasts instead of bypassing the UI input route. | +| Test gameplay that reads `Mouse.current`, button state, delta, or scroll | `simulate-mouse-input` | Injects Input System mouse state into `Mouse.current` so game code observes it like player input. Requires the New Input System (`Input System Package (New)` or `Both`); when that is unavailable, prefer an execute-dynamic-code workaround instead of changing project settings just to use the tool. | +| Jump straight to a known callback, invoke a method, inspect state, or set up a test precondition | `execute-dynamic-code` | Direct automation without reproducing the full input pipeline. | +| Drive custom runtime behavior that does not map cleanly to the built-in input tools | `execute-dynamic-code` | Calls project-specific methods and prototypes one-off flows immediately. | + ## Output Returns JSON: @@ -54,34 +66,3 @@ Returns JSON: - `Diagnostics`: object[] — structured diagnostics; same shape as `CompilationErrors`, usually populated together with it On `Success: false`, inspect `CompilationErrors` first. If empty, read `ErrorMessage` (and `Logs` for extra context) — the failure may be a runtime exception, cancellation, or an "execution in progress" rejection, all of which return empty `CompilationErrors`. Both EditMode and PlayMode are supported targets — the snippet runs in whichever mode the Editor is currently in. - -## Code Examples by Category - -For detailed code examples, refer to these files: - -- **Prefab operations**: See [references/prefab-operations.md](references/prefab-operations.md) - - Create prefabs, instantiate, add components, modify properties -- **Material operations**: See [references/material-operations.md](references/material-operations.md) - - Create materials, set shaders/textures, modify properties -- **Asset operations**: See [references/asset-operations.md](references/asset-operations.md) - - Find/search assets, duplicate, move, rename, load -- **ScriptableObject**: See [references/scriptableobject.md](references/scriptableobject.md) - - Create ScriptableObjects, modify with SerializedObject -- **Scene operations**: See [references/scene-operations.md](references/scene-operations.md) - - Create/modify GameObjects, set parents, wire references, load scenes -- **Batch operations**: See [references/batch-operations.md](references/batch-operations.md) - - Bulk modify objects, batch add/remove components, rename, layer/tag/material replacement -- **Cleanup operations**: See [references/cleanup-operations.md](references/cleanup-operations.md) - - Detect broken scripts, missing references, unused materials, empty GameObjects -- **Undo operations**: See [references/undo-operations.md](references/undo-operations.md) - - Undo-aware operations: RecordObject, AddComponent, SetParent, grouping -- **Selection operations**: See [references/selection-operations.md](references/selection-operations.md) - - Get/set selection, multi-select, filter by type/editability -- **PlayMode automation (PowerShell/Windows)**: See [references/playmode-automation-powershell.md](references/playmode-automation-powershell.md) - - Click UI buttons, invoke methods, set fields, tool combination workflows for PowerShell users -- **PlayMode automation (zsh/macOS)**: See [references/playmode-automation-zsh.md](references/playmode-automation-zsh.md) - - Click UI buttons, invoke methods, set fields, tool combination workflows for zsh users -- **PlayMode UI controls**: See [references/playmode-ui-controls.md](references/playmode-ui-controls.md) - - InputField, Slider, Toggle, Dropdown, drag & drop simulation, list all UI controls -- **PlayMode inspection**: See [references/playmode-inspection.md](references/playmode-inspection.md) - - Scene info, game state via reflection, physics state, raycast checks, GameObject search, position/rotation diff --git a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md deleted file mode 100644 index 0dfc3876b9..0000000000 --- a/.agents/skills/uloop-execute-dynamic-code/references/asset-operations.md +++ /dev/null @@ -1,165 +0,0 @@ -# Asset Operations - -Code examples for AssetDatabase operations using `execute-dynamic-code`. - -## Find Assets - -```csharp -using UnityEditor; -using System.Collections.Generic; - -// By type -string[] guids = AssetDatabase.FindAssets("t:Prefab"); -// By name -// string[] guids = AssetDatabase.FindAssets("Player"); -// By type in folder -// string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets/Prefabs" }); - -List paths = new List(); -foreach (string guid in guids) -{ - paths.Add(AssetDatabase.GUIDToAssetPath(guid)); -} -return $"Found {paths.Count} assets"; -``` - -## Duplicate Asset - -```csharp -using UnityEditor; - -string sourcePath = "Assets/Materials/MyMaterial.mat"; -string destPath = "Assets/Materials/MyMaterial_Backup.mat"; - -bool success = AssetDatabase.CopyAsset(sourcePath, destPath); -return success ? $"Copied to {destPath}" : "Copy failed"; -``` - -## Move Asset - -```csharp -using UnityEditor; - -string sourcePath = "Assets/OldFolder/MyAsset.asset"; -string destPath = "Assets/NewFolder/MyAsset.asset"; - -string error = AssetDatabase.MoveAsset(sourcePath, destPath); -return string.IsNullOrEmpty(error) ? $"Moved to {destPath}" : $"Error: {error}"; -``` - -## Rename Asset (no Undo) - -```csharp -using UnityEditor; - -string assetPath = "Assets/Materials/OldName.mat"; -string newName = "NewName"; - -string error = AssetDatabase.RenameAsset(assetPath, newName); -return string.IsNullOrEmpty(error) ? $"Renamed to {newName}" : $"Error: {error}"; -``` - -## Rename Asset (Undo-supported) - -```csharp -using UnityEditor; - -// ObjectNames.SetNameSmart() supports Undo (AssetDatabase.RenameAsset() does NOT) -Object selected = Selection.activeObject; -if (selected == null) -{ - return "No asset selected"; -} - -string oldName = selected.name; -ObjectNames.SetNameSmart(selected, "NewName"); -AssetDatabase.SaveAssets(); -return $"Renamed {oldName} to {selected.name}"; -``` - -## Get Asset Path from Object - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No object selected"; -} - -string path = AssetDatabase.GetAssetPath(selected); -if (string.IsNullOrEmpty(path)) -{ - return "Selected object is not an asset (scene object)"; -} -return $"Asset path: {path}"; -``` - -## Load Asset at Path - -```csharp -using UnityEditor; - -string path = "Assets/Prefabs/Player.prefab"; -GameObject asset = AssetDatabase.LoadAssetAtPath(path); - -if (asset == null) -{ - return $"Asset not found at {path}"; -} -return $"Loaded: {asset.name}"; -``` - -## Get All Assets of Type - -```csharp -using UnityEditor; - -string[] scriptGuids = AssetDatabase.FindAssets("t:MonoScript"); -int count = 0; - -foreach (string guid in scriptGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - if (path.StartsWith("Assets/")) - { - count++; - } -} -return $"Found {count} scripts in Assets folder"; -``` - -## Check if Asset Exists - -```csharp -using UnityEditor; - -string path = "Assets/Prefabs/Player.prefab"; -string guid = AssetDatabase.AssetPathToGUID(path); - -bool exists = !string.IsNullOrEmpty(guid); -return exists ? $"Asset exists: {path}" : $"Asset not found: {path}"; -``` - -## Get Asset Dependencies - -```csharp -using UnityEditor; - -string assetPath = "Assets/Prefabs/Player.prefab"; -string[] dependencies = AssetDatabase.GetDependencies(assetPath, true); - -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; - -AssetDatabase.Refresh(); -return "AssetDatabase refreshed"; -``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md deleted file mode 100644 index 875c3fc65d..0000000000 --- a/.agents/skills/uloop-execute-dynamic-code/references/batch-operations.md +++ /dev/null @@ -1,399 +0,0 @@ -# Batch Operations - -Code examples for batch processing using `execute-dynamic-code`. - -## Batch Modify Selected Objects - -```csharp -using UnityEditor; -using System.Collections.Generic; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Modify"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj.transform, ""); - obj.transform.localScale = Vector3.one * 2; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Scaled {selected.Length} objects (Single undo step)"; -``` - -## Edit Multiple Objects with SerializedObject - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -List transforms = new List(); -foreach (GameObject obj in selected) -{ - transforms.Add(obj.transform); -} - -SerializedObject serializedObj = new SerializedObject(transforms.ToArray()); -SerializedProperty positionProp = serializedObj.FindProperty("m_LocalPosition"); -positionProp.vector3Value = Vector3.zero; -serializedObj.ApplyModifiedProperties(); - -return $"Reset position of {selected.Length} objects"; -``` - -## Batch Add Component - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Add Rigidbody"); - -int addedCount = 0; -foreach (GameObject obj in selected) -{ - if (obj.GetComponent() == null) - { - Undo.AddComponent(obj); - addedCount++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Added Rigidbody to {addedCount} objects"; -``` - -## Batch Process Assets with StartAssetEditing - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:Material", new[] { "Assets/Materials" }); -if (guids.Length == 0) -{ - return "No materials found"; -} - -AssetDatabase.StartAssetEditing(); -try -{ - int modified = 0; - foreach (string guid in guids) - { - string path = AssetDatabase.GUIDToAssetPath(guid); - Material mat = AssetDatabase.LoadAssetAtPath(path); - if (mat != null) - { - mat.color = Color.white; - EditorUtility.SetDirty(mat); - modified++; - } - } - - AssetDatabase.SaveAssets(); - return $"Reset color of {modified} materials"; -} -finally -{ - AssetDatabase.StopAssetEditing(); -} - -``` - -## Batch Rename GameObjects - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Rename"); - -for (int i = 0; i < selected.Length; i++) -{ - Undo.RecordObject(selected[i], ""); - selected[i].name = $"Item_{i:D3}"; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Renamed {selected.Length} objects"; -``` - -## Batch Set Layer - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int layer = LayerMask.NameToLayer("Default"); - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Layer"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - obj.layer = layer; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Set layer of {selected.Length} objects to Default"; -``` - -## Batch Set Tag - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Tag"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - obj.tag = "Enemy"; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Tagged {selected.Length} objects as Enemy"; -``` - -## Batch Modify ScriptableObjects - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { "Assets/Data" }); -if (guids.Length == 0) -{ - return "No ScriptableObjects found"; -} - -int modified = 0; -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - if (so == null) continue; - - SerializedObject serializedObj = new SerializedObject(so); - SerializedProperty prop = serializedObj.FindProperty("isEnabled"); - if (prop != null) - { - prop.boolValue = true; - serializedObj.ApplyModifiedProperties(); - EditorUtility.SetDirty(so); - modified++; - } -} - -AssetDatabase.SaveAssets(); -return $"Enabled {modified} ScriptableObjects"; -``` - -## Batch Remove Component - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Remove Rigidbody"); - -int removedCount = 0; -foreach (GameObject obj in selected) -{ - Rigidbody rb = obj.GetComponent(); - if (rb != null) - { - Undo.DestroyObjectImmediate(rb); - removedCount++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Removed Rigidbody from {removedCount} objects"; -``` - -## Batch Set Static Flags - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Set Static"); - -foreach (GameObject obj in selected) -{ - Undo.RecordObject(obj, ""); - GameObjectUtility.SetStaticEditorFlags(obj, StaticEditorFlags.BatchingStatic | StaticEditorFlags.OccludeeStatic); -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Set static flags on {selected.Length} objects"; -``` - -## Batch Process with Progress Bar - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:Texture2D"); -if (guids.Length == 0) -{ - return "No textures found"; -} - -int processed = 0; -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; - if (importer != null && importer.maxTextureSize > 1024) - { - importer.maxTextureSize = 1024; - importer.SaveAndReimport(); - processed++; - } - - if (processed % 10 == 0) - { - EditorUtility.DisplayProgressBar("Processing Textures", path, (float)processed / guids.Length); - } -} - -EditorUtility.ClearProgressBar(); -return $"Resized {processed} textures to max 1024"; -``` - -## Batch Align Objects - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length < 2) -{ - return "Select at least 2 objects"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Align Objects"); - -float startX = selected[0].transform.position.x; -float spacing = 2f; - -for (int i = 0; i < selected.Length; i++) -{ - Undo.RecordObject(selected[i].transform, ""); - Vector3 pos = selected[i].transform.position; - pos.x = startX + (i * spacing); - selected[i].transform.position = pos; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Aligned {selected.Length} objects with {spacing}m spacing"; -``` - -## Batch Rename Assets (Undo-supported) - -```csharp -using UnityEditor; - -// ObjectNames.SetNameSmart() supports Undo (AssetDatabase.RenameAsset() does NOT) -Object[] selected = Selection.objects; -if (selected.Length == 0) -{ - return "No assets selected"; -} - -for (int i = 0; i < selected.Length; i++) -{ - string newName = $"{i:D3}_{selected[i].name}"; - ObjectNames.SetNameSmart(selected[i], newName); -} - -AssetDatabase.SaveAssets(); -return $"Renamed {selected.Length} assets"; -``` - -## Batch Replace Material - -```csharp -using UnityEditor; - -GameObject[] selected = Selection.gameObjects; -if (selected.Length == 0) -{ - return "No GameObjects selected"; -} - -string materialPath = "Assets/Materials/NewMaterial.mat"; -Material newMat = AssetDatabase.LoadAssetAtPath(materialPath); -if (newMat == null) -{ - return $"Material not found at {materialPath}"; -} - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Batch Replace Material"); - -int replaced = 0; -foreach (GameObject obj in selected) -{ - MeshRenderer renderer = obj.GetComponent(); - if (renderer != null) - { - Undo.RecordObject(renderer, ""); - renderer.sharedMaterial = newMat; - replaced++; - } -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Replaced material on {replaced} objects"; -``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md deleted file mode 100644 index 1c2e023f0a..0000000000 --- a/.agents/skills/uloop-execute-dynamic-code/references/cleanup-operations.md +++ /dev/null @@ -1,348 +0,0 @@ -# Cleanup Operations - -Code examples for project cleanup operations using `execute-dynamic-code`. - -## Detect Missing Scripts on GameObject - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -int missingCount = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(selected); -return $"{selected.name} has {missingCount} missing script(s)"; -``` - -## Remove Missing Scripts from GameObject - -```csharp -using UnityEditor; - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -int removedCount = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(selected); -return $"Removed {removedCount} missing script(s) from {selected.name}"; -``` - -## Scan Scene for Missing Scripts - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List objectsWithMissing = new List(); - -foreach (GameObject obj in allObjects) -{ - int count = GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(obj); - if (count > 0) - { - objectsWithMissing.Add($"{obj.name} ({count})"); - } -} - -if (objectsWithMissing.Count == 0) -{ - return "No missing scripts found in scene"; -} - -return $"Objects with missing scripts: {string.Join(", ", objectsWithMissing)}"; -``` - -## Remove All Missing Scripts from Scene - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -int totalRemoved = 0; - -int undoGroup = Undo.GetCurrentGroup(); -Undo.SetCurrentGroupName("Remove All Missing Scripts"); - -foreach (GameObject obj in allObjects) -{ - int removed = GameObjectUtility.RemoveMonoBehavioursWithMissingScript(obj); - totalRemoved += removed; -} - -Undo.CollapseUndoOperations(undoGroup); -return $"Removed {totalRemoved} missing scripts from scene"; -``` - -## Scan for Missing References - -Scans the entire scene by default. Set `scanSelectionOnly` to `true` only when the task asks for the selected GameObject. - -```csharp -using UnityEditor; - -bool scanSelectionOnly = false; -GameObject selected = Selection.activeGameObject; -if (scanSelectionOnly && selected == null) -{ - return "No GameObject selected"; -} - -GameObject[] targets = scanSelectionOnly - ? new[] { selected } - : Object.FindObjectsByType(FindObjectsSortMode.None); - -List results = new List(); - -foreach (GameObject obj in targets) -{ - Component[] components = obj.GetComponents(); - foreach (Component comp in components) - { - if (comp == null) continue; - - SerializedObject so = new SerializedObject(comp); - SerializedProperty prop = so.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - results.Add($"{obj.name}/{comp.GetType().Name}.{prop.name}"); - } - } - } - } -} - -if (results.Count == 0) -{ - return "No missing references found"; -} - -return $"Missing references ({results.Count}): {string.Join(", ", results.Take(10))}"; -``` - -## Find Unused Materials in Project - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string[] materialGuids = AssetDatabase.FindAssets("t:Material"); -HashSet usedMaterials = new HashSet(); - -string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab"); -foreach (string guid in prefabGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - string[] deps = AssetDatabase.GetDependencies(path, true); - foreach (string dep in deps) - { - if (dep.EndsWith(".mat")) - { - usedMaterials.Add(dep); - } - } -} - -// This scan only checks prefab dependencies. Verify scene and other asset -// references manually before deleting any reported materials. -List unusedMaterials = new List(); -foreach (string guid in materialGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - if (!usedMaterials.Contains(path)) - { - unusedMaterials.Add(path); - } -} - -return $"Found {unusedMaterials.Count} materials not referenced by prefabs. Verify scene and other asset references manually before deleting."; -``` - -## Find Duplicate Names in Hierarchy - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -Dictionary nameCounts = new Dictionary(); - -foreach (GameObject obj in allObjects) -{ - if (nameCounts.ContainsKey(obj.name)) - { - nameCounts[obj.name]++; - } - else - { - nameCounts[obj.name] = 1; - } -} - -List duplicates = new List(); -foreach (KeyValuePair kvp in nameCounts) -{ - if (kvp.Value > 1) - { - duplicates.Add($"{kvp.Key} ({kvp.Value})"); - } -} - -if (duplicates.Count == 0) -{ - return "No duplicate names found"; -} - -return $"Duplicate names: {string.Join(", ", duplicates.Take(15))}"; -``` - -## Check for Broken Prefab Instances - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List brokenPrefabs = new List(); - -foreach (GameObject obj in allObjects) -{ - if (PrefabUtility.IsPartOfPrefabInstance(obj)) - { - GameObject prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(obj); - if (prefabAsset == null) - { - brokenPrefabs.Add(obj.name); - } - } -} - -if (brokenPrefabs.Count == 0) -{ - return "No broken prefab instances found"; -} - -return $"Broken prefab instances: {string.Join(", ", brokenPrefabs)}"; -``` - -## Find Objects with Negative Scale - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List negativeScale = new List(); - -foreach (GameObject obj in allObjects) -{ - Vector3 scale = obj.transform.localScale; - if (scale.x < 0 || scale.y < 0 || scale.z < 0) - { - negativeScale.Add($"{obj.name} ({scale})"); - } -} - -if (negativeScale.Count == 0) -{ - return "No objects with negative scale found"; -} - -return $"Negative scale objects: {string.Join(", ", negativeScale.Take(10))}"; -``` - -## Find Empty Leaf GameObjects - -```csharp -using UnityEditor; - -GameObject[] allObjects = Object.FindObjectsByType(FindObjectsSortMode.None); -List emptyObjects = new List(); -foreach (GameObject obj in allObjects) -{ - if (obj == null) continue; - - Component[] components = obj.GetComponents(); - if (components.Length == 1 && obj.transform.childCount == 0) - { - emptyObjects.Add(obj.name); - } -} - -if (emptyObjects.Count == 0) -{ - return "No empty leaf GameObjects found"; -} - -return $"Empty leaf GameObjects ({emptyObjects.Count}): {string.Join(", ", emptyObjects.Take(20))}"; -``` - -## Find Large Meshes - -```csharp -using UnityEditor; - -string[] meshGuids = AssetDatabase.FindAssets("t:Mesh"); -List largeMeshes = new List(); -int threshold = 10000; - -foreach (string guid in meshGuids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - Mesh mesh = AssetDatabase.LoadAssetAtPath(path); - if (mesh != null && mesh.vertexCount > threshold) - { - largeMeshes.Add($"{path} ({mesh.vertexCount} verts)"); - } -} - -if (largeMeshes.Count == 0) -{ - return $"No meshes with more than {threshold} vertices found"; -} - -return $"Large meshes: {string.Join(", ", largeMeshes.Take(10))}"; -``` - -## Validate Asset References - -```csharp -using UnityEditor; - -string[] guids = AssetDatabase.FindAssets("t:ScriptableObject", new[] { "Assets/Data" }); -List invalidRefs = new List(); - -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - ScriptableObject so = AssetDatabase.LoadAssetAtPath(path); - if (so == null) continue; - - SerializedObject serializedObj = new SerializedObject(so); - SerializedProperty prop = serializedObj.GetIterator(); - - while (prop.NextVisible(true)) - { - if (prop.propertyType == SerializedPropertyType.ObjectReference) - { - if (prop.objectReferenceValue == null && prop.objectReferenceInstanceIDValue != 0) - { - invalidRefs.Add($"{path}: {prop.name}"); - } - } - } -} - -if (invalidRefs.Count == 0) -{ - return "All asset references are valid"; -} - -return $"Invalid references ({invalidRefs.Count}): {string.Join(", ", invalidRefs.Take(10))}"; -``` diff --git a/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md b/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md deleted file mode 100644 index 4defb5c2fe..0000000000 --- a/.agents/skills/uloop-execute-dynamic-code/references/material-operations.md +++ /dev/null @@ -1,172 +0,0 @@ -# Material Operations - -Code examples for Material operations using `execute-dynamic-code`. - -## Create a New Material - -```csharp -using UnityEditor; - -Shader shader = Shader.Find("Standard"); -Material mat = new Material(shader); -mat.name = "MyMaterial"; -string path = "Assets/Materials/MyMaterial.mat"; -AssetDatabase.CreateAsset(mat, path); -AssetDatabase.SaveAssets(); -return $"Material created at {path}"; -``` - -## Set Material Color - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.SetColor("_Color", new Color(1f, 0.5f, 0f, 1f)); -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Material color set to orange"; -``` - -## Set Material Properties (Float, Vector) - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.SetFloat("_Metallic", 0.8f); -mat.SetFloat("_Glossiness", 0.6f); -mat.SetVector("_EmissionColor", new Vector4(1, 1, 0, 1)); - -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Material properties updated"; -``` - -## Assign Texture to Material - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -string texPath = "Assets/Textures/MyTexture.png"; - -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -Texture2D tex = AssetDatabase.LoadAssetAtPath(texPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} -if (tex == null) -{ - return $"Texture not found at {texPath}"; -} - -mat.SetTexture("_MainTex", tex); -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return $"Assigned {tex.name} to material"; -``` - -## Assign Material to GameObject - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -GameObject selected = Selection.activeGameObject; -if (selected == null) -{ - return "No GameObject selected"; -} - -Renderer renderer = selected.GetComponent(); -if (renderer == null) -{ - return "Selected object has no Renderer"; -} - -renderer.sharedMaterial = mat; -EditorUtility.SetDirty(selected); -return $"Assigned {mat.name} to {selected.name}"; -``` - -## Enable/Disable Material Keywords - -```csharp -using UnityEditor; - -string matPath = "Assets/Materials/MyMaterial.mat"; -Material mat = AssetDatabase.LoadAssetAtPath(matPath); -if (mat == null) -{ - return $"Material not found at {matPath}"; -} - -mat.EnableKeyword("_EMISSION"); -mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; - -EditorUtility.SetDirty(mat); -AssetDatabase.SaveAssets(); -return "Emission enabled on material"; -``` - -## Find All Materials Using a Shader - -```csharp -using UnityEditor; -using System.Collections.Generic; - -string shaderName = "Standard"; -string[] guids = AssetDatabase.FindAssets("t:Material"); -List matchingMaterials = new List(); - -foreach (string guid in guids) -{ - string path = AssetDatabase.GUIDToAssetPath(guid); - Material mat = AssetDatabase.LoadAssetAtPath(path); - if (mat != null && mat.shader != null && mat.shader.name == shaderName) - { - matchingMaterials.Add(path); - } -} -return $"Found {matchingMaterials.Count} materials using {shaderName}"; -``` - -## Duplicate Material - -```csharp -using UnityEditor; - -string sourcePath = "Assets/Materials/MyMaterial.mat"; -string destPath = "Assets/Materials/MyMaterial_Copy.mat"; - -Material source = AssetDatabase.LoadAssetAtPath(sourcePath); -if (source == null) -{ - return $"Material not found at {sourcePath}"; -} - -Material copy = new Material(source); -AssetDatabase.CreateAsset(copy, destPath); -AssetDatabase.SaveAssets(); -return $"Material duplicated to {destPath}"; -``` 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 deleted file mode 100644 index aeab072a4b..0000000000 --- a/.agents/skills/uloop-execute-dynamic-code/references/playmode-automation-powershell.md +++ /dev/null @@ -1,342 +0,0 @@ -# PlayMode Automation (PowerShell) - -Code examples for runtime automation during Play mode using `execute-dynamic-code`. -These examples manipulate live scene objects while the game is running. -Shell command examples in this file target `PowerShell`. - -## When to use dedicated mouse simulation tools instead - -The examples in this file call UI handlers or runtime methods from C#. -That remains the better choice when you want targeted automation, direct state control, or a quick diagnostic path. -Use dedicated mouse tools only when the input route itself is part of what you need to verify. - -Choose the tool based on what you are trying to validate: - -| Scenario | Recommended tool | Why | -|----------|------------------|-----| -| Verify that a uGUI element responds through the real EventSystem pointer path | `simulate-mouse-ui` | Fires `PointerDown` / `PointerUp` / `PointerClick` / drag events through EventSystem raycasts instead of bypassing the UI input route. | -| Test gameplay that reads `Mouse.current`, button state, delta, or scroll | `simulate-mouse-input` | Injects Input System mouse state into `Mouse.current`, so game code can observe `wasPressedThisFrame`, movement delta, and scroll like player input. This assumes the project uses the New Input System (`Input System Package (New)` or `Both`). If that is not available in the target project, prefer `execute-dynamic-code` for a project-specific workaround instead of changing project settings just to use this tool. | -| Jump straight to a known button callback, invoke a method, inspect state, or set up a test precondition | `execute-dynamic-code` | Best when you intentionally want direct automation without reproducing the full input pipeline. | -| Drive custom runtime behavior that does not map cleanly to the built-in mouse tools | `execute-dynamic-code` | Lets you call project-specific methods, inspect scene objects, and prototype one-off flows immediately. | - -In short: do not default everything to mouse simulation. -Use `execute-dynamic-code` for direct automation and diagnostics, and switch to `simulate-mouse-ui` or `simulate-mouse-input` when reproducing the real input path is the thing you need to test. - -## PowerShell 7 Multiline Snippets - -Default to a single-quoted here-string variable in PowerShell 7 (`pwsh`), then pass it to `--code`. - -```powershell -$code = @' -using UnityEngine; -using UnityEngine.SceneManagement; - -Scene scene = SceneManager.GetActiveScene(); -GameObject[] objects = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); -return $"{scene.name}: {objects.Length} objects"; -'@ - -uloop execute-dynamic-code --code $code -``` - -### With parameters - -Pass a JSON object literal, not a JSON string value. Use a second here-string when the parameters are easier to read on separate lines. - -```powershell -$code = @' -return parameters["param0"]; -'@ - -$parameters = @' -{"param0":"Hello from PowerShell"} -'@ - -uloop execute-dynamic-code --code $code --parameters $parameters -``` - -## Short Inline Snippets - -PowerShell 7 (`pwsh`) preserves C# double quotes inside single-quoted native command arguments. - -```powershell -uloop execute-dynamic-code --code 'return "Hello from PowerShell";' -``` - -If the C# snippet itself contains a single quote, double it inside the PowerShell single-quoted string. - -```powershell -uloop execute-dynamic-code --code 'char initial = ''A''; return initial.ToString();' -``` - -Wrap JSON object literals for `--parameters` in single quotes so PowerShell passes inner double quotes unchanged. - -```powershell -uloop execute-dynamic-code --code 'return parameters["param0"];' --parameters '{"param0":"Hello from PowerShell"}' -``` - -## Windows PowerShell 5.1 - -Windows PowerShell 5.1 removes unescaped double quotes when invoking native commands. Escape C# double quotes as `\"` in short inline snippets. - -```powershell -uloop execute-dynamic-code --code 'return \"Hello from Windows PowerShell\";' -``` - -For multiline or string-heavy snippets, prefer `--code-file`. - -## Native Launcher Check - -Multiline `--code` expects the native Go `uloop.exe`. If `Get-Command uloop` resolves to an old `.cmd` shim, run `uloop install` and open a new terminal. - -```powershell -(Get-Command uloop).Source -``` - -## Click UI Button by Path - -```csharp -using UnityEngine.UI; - -Button btn = GameObject.Find("Canvas/StartButton")?.GetComponent