unity-editor
Control and observe the Unity Editor state
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "unity-editor"
description: "Control and observe the Unity Editor state"
license: "MIT"
---
> **Before calling any skill in this module:** if you are about to call a skill with parameters guessed from its name or description, STOP — read this file (or fetch its schema via `GET /skills/recommend?includeSchema=true`) first. If you already have the parameter definitions from recommend/schema, you may proceed straight to dryRun.
## Triggers
- Reading persistent scene/file changes
- Driving play mode
- Inspecting runtime state
- Executing menu items
- 读取持久化场景/文件变更、操控 Play Mode、检查运行时状态、执行菜单项
# Unity Editor Skills
Observe and control the Unity Editor without parsing scene YAML.
## Operating Mode
- **Approval**:本模块 Mixed —— `editor_get_changes` / `editor_get_selection` / `editor_get_context` / `editor_get_state` / `editor_get_tags` / `editor_get_layers` / `editor_playmode_inspect` 标 `SkillMode.SemiAuto`,可直接执行;其余 `editor_select` / `editor_undo` / `editor_redo` / `editor_playmode_step` 默认 FullAuto,Approval 模式下需 grant。
- **Auto / Bypass**:FullAuto 直接执行。
- **含 NeverInSemi 高危 skill**:`editor_play` / `editor_play_capture` / `editor_stop` / `editor_pause`(标 `MayEnterPlayMode = true`);`editor_execute_menu`(标 `MayTriggerReload = true` —— `Assets/Refresh`、`Assets/Reimport All` 之类的菜单项会触发域重载)。这些在 Approval **和** Auto 下都返 `MODE_FORBIDDEN`,仅 Bypass 或 Allowlist 命中可调;**grant 流程对它们不适用**,不要尝试。
**DO NOT** (common hallucinations):
- `editor_run` does not exist → use `editor_play` to enter play mode
- `editor_compile` / `editor_recompile` do not exist → use `debug_force_recompile`
- `editor_save` does not exist → use `editor_execute_menu` with menuPath `"File/Save"`
- `editor_execute_menu` requires exact menu path — typos cause silent failure
**Routing**:
- For compilation check → use `debug` module's `debug_check_compilation`
- For console errors → use `debug` module's `debug_get_errors`
- For scene save → `scene_save` (scene module) or `editor_execute_menu` menuPath="File/Save"
- When file watching reports changes, or the AI resumes after the user edited Unity → call `editor_get_changes` before reading `.unity` YAML
- If `editor_get_changes.dropped=true` → its 500-entry retention window was exceeded; rebuild context with `scene_context` / `scene_diff`
## Skills Overview
| Skill | Description |
|-------|-------------|
| `editor_play` | Enter play mode |
| `editor_play_capture` | Observe runtime errors, optionally screenshot, then exit |
| `editor_stop` | Exit play mode |
| `editor_pause` | Toggle pause |
| `editor_playmode_step` | Advance Play Mode by N frames (async job) |
| `editor_playmode_inspect` | Inspect a GameObject's live runtime state (transform, component fields) |
| `editor_select` | Select GameObject |
| `editor_get_selection` | Get selected objects |
| `editor_get_context` | Get full editor context (selection, assets, scene) |
| `editor_get_changes` | Read persistent scene/file changes by cursor |
| `editor_undo` | Undo last action |
| `editor_redo` | Redo last action |
| `editor_get_state` | Get editor state |
| `editor_execute_menu` | Execute menu item |
| `editor_get_tags` | Get all tags |
| `editor_get_layers` | Get all layers |
| `console_set_pause_on_error` | Pause play mode on error (console module) |
---
## Skills
### editor_play
Enter play mode. Warning: any unsaved scene changes made during Play mode will be lost when exiting.
**Returns**: `{success, mode, jobId}` — `mode="playing"`, `jobId` returned from `AsyncJobService` so callers can poll `entering_play_mode` completion.
### editor_play_capture
Enter Play Mode, observe errors for `durationSeconds` (default 10, range 1–300), optionally capture the Game View, then exit. Returns a Job whose result includes `healthy`, error aggregates, `stoppedEarly`, and `screenshotPath`.
### editor_stop
Exit play mode.
**Returns**: `{success, mode}` — `mode="stopped"`.
### editor_pause
Toggle pause state.
**Returns**: `{success, paused}` — `paused` is the new boolean state.
### editor_playmode_step
Advance Play Mode forward by `frames` (1–100, default 1) using `EditorApplication.Step`; Unity automatically enters paused state as part of stepping. Requires Play Mode to already be active — call `editor_play` or `editor_play_capture` first, otherwise this returns a structured error (`error` + `hint` + `suggestedSkills: ["editor_play", "editor_play_capture"]`). Only one step job may be in flight at a time; a second call while one is still running returns an error naming the active `jobId`.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `frames` | int | No | 1 | Frames to advance, clamped to 1–100 |
**Runs as an async job, not synchronously**: `EditorApplication.Step()` only lands on a later Editor tick, so the skill issues one `Step()` call at a time, confirms it landed by watching `Time.frameCount` advance, and only then issues the next — back-to-back `Step()` calls without that confirmation are not reliable. The call itself returns immediately: `{success, status: "accepted", jobId, framesRequested}`.
Poll `job_status` with the returned `jobId` until `status="completed"`; its `details` then contains `{framesRequested, framesCompleted, frameCount, isPaused}`. **Do not use `job_wait`** — like `playmode`/`play_capture`, this job's progress depends on `EditorApplication.update` ticks, and `job_wait`'s blocking loop runs on the same main thread those ticks come from, so it cannot observe progress; it will just spend its full timeout doing nothing. If Play Mode exits or Unity fails to advance a frame within 10s, the job fails with `failed_exited_play_mode` / `failed_step_timeout`.
### editor_playmode_inspect
Inspect a GameObject's live runtime state: transform (position/rotation/scale), `activeSelf`/`activeInHierarchy`, and — when `componentType` is given — that component's public fields and properties (reuses the same reflection helper as `component_get_properties`, not reimplemented). Works during Play Mode, including while paused, and also in Edit Mode, where it returns editor-time values — check `isPlaying`/`isPaused` in the response to know which state the values reflect.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | No* | Object name |
| `instanceId` | int | No* | Instance ID (preferred) |
| `path` | string | No* | Object path |
| `componentType` | string | No | Component type name; when set, also returns its public fields/properties |
*One identifier required
**Returns**: `{success, gameObject, entityId, instanceId, path, activeSelf, activeInHierarchy, transform: {position, localPosition, rotation, localScale}, isPlaying, isPaused, component}`. `component` is `null` when `componentType` is omitted, an error object when the type isn't found or isn't attached, or `{gameObject, component, fullTypeName, properties, fields}` (same shape as `component_get_properties`) otherwise.
### editor_select
Select a GameObject.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | No* | Object name |
| `instanceId` | int | No* | Instance ID (preferred) |
| `path` | string | No* | Object path |
*One identifier required
### editor_get_selection
Get currently selected objects.
**Returns**: `{count, objects: [{name, instanceId}]}`
### editor_get_context
Get full editor context including selection, assets, and scene info.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `includeComponents` | bool | No | false | Include component list |
| `includeChildren` | bool | No | false | Include children info |
**Returns**:
- `selectedGameObjects`: Objects in Hierarchy (instanceId, path, tag, layer)
- `selectedAssets`: Assets in Project window (GUID, path, type, isFolder)
- `activeScene`: Current scene info (name, path, isDirty)
- `focusedWindow`: Name of focused editor window
- `isPlaying`, `isCompiling`: Editor state
### editor_get_changes
Read the persistent change journal at `Library/UnitySkills/editor_changes.jsonl`. It captures scene object/component/property summaries plus imported, deleted, and moved asset paths across Domain Reloads. It never parses or returns raw `.unity` YAML.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `since` | long | No | `0` | Previous response cursor; `0` reads retained history |
| `types` | string | No | `all` | Comma-separated `scene`, `file`, `undo`, or `lifecycle` |
| `source` | string | No | `all` | `editor`/`manual`, `rest`, or `all` |
| `limit` | int | No | `100` | Newest entries to return, 1-500 |
**Returns:** `{hasChanges, cursor, oldestSeq, dropped, truncated, changes}`. Save `cursor` for the next call. `dropped=true` means older changes fell outside the 500-entry journal, so rebuild full scene context.
### editor_undo
Undo the last action.
### editor_redo
Redo the last undone action.
### editor_get_state
Get current editor state.
**Returns**: `{isPlaying, isPaused, isCompiling, timeSinceStartup, unityVersion, platform}`
### editor_execute_menu
Execute a menu command.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `menuPath` | string | Yes | Menu item path |
**Common Menu Paths**:
| Menu Path | Action |
|-----------|--------|
| `File/Save` | Save current scene |
| `File/Build Settings...` | Open build settings |
| `Edit/Play` | Toggle play mode |
| `GameObject/Create Empty` | Create empty object |
| `Window/General/Console` | Open console |
| `Assets/Refresh` | Refresh assets |
### editor_get_tags
Get all available tags.
**Returns**: `{tags: [string]}`
### editor_get_layers
Get all available layers.
**Returns**: `{layers: [{index, name}]}`
### Pause On Error
Pause-on-error is provided by the console module, not the editor module.
Use `console_set_pause_on_error` from [console/SKILL.md](/E:/CodeSpace/Unity-Skills/SkillsForUnity/unity-skills~/skills/console/SKILL.md).
---
## Example Usage
```python
import unity_skills
# Check editor state before operations
state = unity_skills.call_skill("editor_get_state")
if state['isCompiling']:
print("Wait for compilation to finish")
# On resume or after file-watch notification, inspect semantic changes first.
changes = unity_skills.call_skill("editor_get_changes", since=last_cursor)
last_cursor = changes["cursor"]
# Get full context (useful for understanding current state)
context = unity_skills.call_skill("editor_get_context", includeComponents=True)
for obj in context['selectedGameObjects']:
print(f"Selected: {obj['name']} (ID: {obj['instanceId']})")
# Select and operate on object
unity_skills.call_skill("editor_select", name="Player")
selection = unity_skills.call_skill("editor_get_selection")
# Safe experimentation with undo
unity_skills.call_skill("gameobject_delete", name="TestObject")
unity_skills.call_skill("editor_undo") # Restore if needed
# Execute menu command
unity_skills.call_skill("editor_execute_menu", menuPath="File/Save")
# Step through Play Mode frame-by-frame and assert state changed
import time
# Enter Play Mode with a generous observation window so there's time left to step
capture = unity_skills.call_skill("editor_play_capture", durationSeconds=30)
while True:
capture_status = unity_skills.call_skill("job_status", jobId=capture["jobId"])
if capture_status["currentStage"] not in ("entering_play_mode", "domain_reload_recovery"):
break # Play Mode has been entered; capture_status["currentStage"] == "observing"
time.sleep(0.2)
before = unity_skills.call_skill("editor_playmode_inspect", name="Player")["transform"]["position"]
step = unity_skills.call_skill("editor_playmode_step", frames=3)
while True:
step_status = unity_skills.call_skill("job_status", jobId=step["jobId"]) # not job_wait, see above
if step_status["status"] in ("completed", "failed"):
break
time.sleep(0.1)
after = unity_skills.call_skill("editor_playmode_inspect", name="Player")["transform"]["position"]
assert after != before, "Player did not move after 3 frames"
print(step_status["details"]["frameCount"], step_status["details"]["isPaused"])
```
## Best Practices
1. Check editor state before play mode operations
2. Don't modify scene during play mode (changes lost)
3. Use undo for safe experimentation
4. On resume, call `editor_get_changes` before reading scene files
5. Use `editor_get_context` to get instanceId for batch operations
6. Menu commands must match exact paths
## Exact Signatures
Exact names, parameters, defaults, and returns are defined by `GET /skills/schema` or `unity_skills.get_skill_schema()`, not by this file.
## Common Errors
Full transport-level codes (COMPILING/RATE_LIMIT etc.) → ../../references/protocol-error-codes.md
| Error | Trigger | Fix |
|---|---|---|
| `TARGET_NOT_FOUND` | The requested GameObject or menu item could not be found (e.g., `Menu item not found or failed`). | Verify the object with `gameobject_find` / `scene_get_hierarchy`, or check the exact menu path spelling before retrying. |
| `SKILL_ERROR` | A play-mode state conflict occurred, such as `Already in play mode`, `Not in play mode`, or an active frame-step job already exists. | Match the editor state to the skill requirement: enter/exit play mode first, or wait for the existing step job to finish. |More General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

