diff --git a/.github/workflows/unity-tests.yml b/.github/workflows/unity-tests.yml new file mode 100644 index 00000000..f3c0ac9c --- /dev/null +++ b/.github/workflows/unity-tests.yml @@ -0,0 +1,85 @@ +name: Unity Tests + +on: + pull_request: + branches: + - main + paths: + - "GGJ26/**" + - ".github/workflows/unity-tests.yml" + workflow_dispatch: + +permissions: + contents: read + checks: write + +jobs: + unity-tests: + name: Unity Tests + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + sudo docker image prune --all --force + + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + + - name: Cache Library + uses: actions/cache@v4 + with: + path: GGJ26/Library + key: Library-${{ hashFiles('GGJ26/Assets/**', 'GGJ26/Packages/manifest.json', 'GGJ26/ProjectSettings/**') }} + restore-keys: | + Library- + + - name: Run EditMode tests + id: editmode + uses: game-ci/unity-test-runner@v4 + env: + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + with: + projectPath: GGJ26 + testMode: EditMode + unityVersion: auto + githubToken: ${{ secrets.GITHUB_TOKEN }} + checkName: EditMode Test Results + + - name: Upload EditMode artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: unity-editmode-results + path: ${{ steps.editmode.outputs.artifactsPath }} + if-no-files-found: warn + retention-days: 7 + + - name: Run PlayMode tests + id: playmode + if: always() + uses: game-ci/unity-test-runner@v4 + env: + UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + with: + projectPath: GGJ26 + testMode: PlayMode + unityVersion: auto + githubToken: ${{ secrets.GITHUB_TOKEN }} + checkName: PlayMode Test Results + + - name: Upload PlayMode artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: unity-playmode-results + path: ${{ steps.playmode.outputs.artifactsPath }} + if-no-files-found: warn + retention-days: 7 diff --git a/.gitignore b/.gitignore index b03f25c4..55cce0e4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,12 @@ # Antigravity .agent/ .gemini/ -scripts/ \ No newline at end of file +scripts/ + +# Claude code +.claude/ +CLAUDE.md +.omc/ + +# Codex +.codex/ \ No newline at end of file diff --git a/GGJ26/.claude/CLAUDE.md b/GGJ26/.claude/CLAUDE.md deleted file mode 100644 index f0c82713..00000000 --- a/GGJ26/.claude/CLAUDE.md +++ /dev/null @@ -1,104 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Multiplayer hide-and-seek game built with **Unity 6** (6000.0.58f2) and **Photon Fusion 2** (Shared Mode). One "Seeker" hunts "Hiders" who wear colored masks (Red/Blue/Green). Hiders survive a 180-second timer while performing periodic dances; the Seeker eliminates them with a stun gun. - -## Game Design Documents - -Design docs are located in `../GameDesign/`. Designs change frequently — always check the latest docs before implementing. -- `Main_Design.md` — Core game design -- `Required_Resource.md` — Required resources -- `UI_Flow.md` — UI flow - -## MCP Servers - -- **Unity MCP** — Direct interaction with the Unity Editor (scene queries, GameObject/component manipulation, script editing, console checks, play mode control, etc.). -- **Context7 MCP** — Search up-to-date documentation and code examples for libraries/frameworks. Use when you need docs for Fusion 2, Unity API, etc. - -## Build & Run - -- Open in Unity 6 (version 6000.0.58f2) -- Play via Unity Editor Play button -- Build via **File > Build Profiles** using `Assets/Settings/Build Profiles/Windows.asset` -- No CLI build scripts or automated tests exist -- Input actions regenerate if `.inputactions` files change - -## Verification Workflow - -After any code change, follow this sequence: -1. **Compile check** — Run `read_console` via Unity MCP to verify no compilation errors -2. **Scene check** — If you modified scene-related logic, use `manage_scene(action="get_hierarchy")` to verify expected state -3. **Console monitor** — Check `read_console` for runtime errors/warnings after play-testing - -## Unity Gotchas (Quick Reference) - -- Never delete/move files without their `.meta` pair -- Never edit `Library/`, `Temp/`, `Logs/` directories -- Use Unity MCP tools to edit `.prefab`, `.unity`, `.asset` files — do not raw-edit -- After script changes, always `read_console` for compilation errors -- See `.claude/rules/unity-gotchas.md` for the full list - -## Language - -All plans, documents, and written content (including CLAUDE.md, commit messages, PR descriptions, code comments) must be written in English. - -## Code Style - -- Allman braces, 4-space indentation -- PascalCase for types/methods, camelCase for fields -- `[SerializeField] private` for inspector wiring -- Place new scripts in the appropriate feature folder under `Assets/01. Scripts/` - -## Git Conventions - -### Tags - -| Tag | Description | -|-----|-------------| -| `feat` | New code / feature addition | -| `fix` | Bug fix | -| `refact` | Code refactoring | -| `comment` | Comment additions or typo fixes (no code changes) | -| `docs` | Documentation changes (README, CLAUDE.md, etc.) | -| `art` | Art asset additions | -| `merge` | Merge commits | -| `rename` | File/folder renames or moves | -| `chore` | Package additions, config changes, etc. | - -### Branch Naming - -``` -(TAG)/(summary)/(ISSUE_NUMBER if applicable) - -Examples: - feat/player/#99 - chore/package -``` - -### Commit Messages - -``` -(TAG)(ISSUE_NUMBER if applicable) : Title (capitalize first letter if English) - -Examples: - feat(#123) : Implement player movement - - - Modified Player.cs - - Added input handling - - --- - - chore : Add DOTween package -``` - -### PR Merge Convention - -``` -title: (TAG)/(ISSUE_NUMBER) (PR_NUMBER) - -Example: - FEAT/35 (#40) -``` diff --git a/GGJ26/.claude/commands/update-docs.md b/GGJ26/.claude/commands/update-docs.md deleted file mode 100644 index b80244da..00000000 --- a/GGJ26/.claude/commands/update-docs.md +++ /dev/null @@ -1,42 +0,0 @@ -Audit and update the `.claude/` documentation files against the actual codebase. Read all three docs, scan the codebase for discrepancies, report findings, and fix anything outdated. - -## Steps - -### 1. Read the current documentation -Read these files: -- `.claude/CLAUDE.md` -- `.claude/rules/architecture.md` -- `.claude/rules/unity-gotchas.md` - -### 2. Gather ground truth from the codebase - -Read these files for version/config data: -- `ProjectSettings/ProjectVersion.txt` — actual Unity version -- `Packages/manifest.json` — actual package versions (Fusion, Input System, Cinemachine, AI Navigation, URP, VFX Graph) -- `Assets/Settings/Build Profiles/` — verify build profile path exists - -Scan the codebase for structural data: -- `Assets/00. Scenes/` — list actual scene files to verify scene flow -- `Assets/01. Scripts/` — list top-level feature folders to verify directory layout -- Check key classes mentioned in architecture.md still exist and match their described responsibilities: - - `FusionLauncher`, `FusionSessionFlow`, `FusionSpawnService`, `FusionRoleAssignmentService` - - `PlayerRole`, `PlayerStateManager`, `PlayerElimination`, `StunGun` - - `DanceEventPublisher`, `GameStateController` - - `BaseNPC`, `RedNPC`, `BlueNPC`, `GreenNPC`, `NPCController` - - `AudioManager`, `SoundEmitter` - - `SaveLoadSystem`, `FileManager` -- Verify enum values (e.g. GameState enum), timer values, NPC counts, cooldown values mentioned in docs by reading the relevant source files -- Check event channel SO types listed in architecture.md against actual classes in `Assets/01. Scripts/Events/` - -### 3. Report findings - -For each documentation file, report: -- **Accurate**: Items that match the codebase -- **Outdated/Wrong**: Items that don't match, with the actual value from the codebase -- **Missing**: Important systems or patterns in the codebase not documented - -### 4. Fix discrepancies - -For every outdated or wrong item found, edit the documentation file to match the current codebase. Do NOT change the codebase — only update docs. - -After making changes, show a summary of all edits made. diff --git a/GGJ26/.claude/rules/architecture.md b/GGJ26/.claude/rules/architecture.md deleted file mode 100644 index 851c291b..00000000 --- a/GGJ26/.claude/rules/architecture.md +++ /dev/null @@ -1,74 +0,0 @@ -# Architecture Reference - -## Scene Flow - -Lobby → WaitingRoom (matchmaking/ready-up) → GameScene (gameplay) - -Scene loading is managed by `FusionSessionFlow`, which creates/destroys the `NetworkRunner` and handles async scene transitions. - -## Core Systems - -### Networking (Fusion 2 Shared Mode) -- `FusionLauncher` — Entry point, singleton, implements `INetworkRunnerCallbacks` -- `FusionSessionFlow` — Session lifecycle and scene loading -- `FusionSpawnService` — Spawns players at grid positions and 27 NPCs (9 per color) -- `FusionRoleAssignmentService` — Deterministic seeker selection (lowest PlayerRef) -- `PlayerRole` — Mask color assignment (seeded Fisher-Yates shuffle) -- Authority pattern: state authority for game logic RPCs, input authority for per-player input - -### Event System (ScriptableObject Channels) -All inter-system communication uses SO event channels (`VoidEventChannelSO`, `IntEventChannelSO`, `AudioCueEventChannelSO`, etc.). Systems subscribe in `OnEnable()` and unsubscribe in `OnDisable()`. Events fire via `RaiseEvent()`. Channel assets live in `Assets/02. ScriptableObjects/Events/`. - -### Game State -Enum-driven (`None`, `Gameplay`, `Pause`, `Menu`, `CutScene`, `Ending`). `GameStateController` manages transitions, input toggling, and `Time.timeScale`. - -### Dance System -`DanceEventPublisher` publishes mask-specific dances (8-12s interval, 3s duration) and group dances (30s interval, 10s duration). Dance index is announced before execution for synchronized animation across clients. - -### Player System -- `PlayerStateManager` — Tracks all player roles/deaths via networked RPCs; has `PlayerStateFallback` for offline testing -- `PlayerElimination` — Handles death, spectator camera spawning -- `StunGun` — Seeker weapon (5s cooldown, 6m raycast) - -### NPC System -`BaseNPC` (abstract) → `RedNPC`/`BlueNPC`/`GreenNPC` with NavMeshAgent pathfinding. `Local/` variants exist for testing without network. `NPCController` syncs position/animation over network. - -### Audio -`AudioManager` singleton with object-pooled `SoundEmitter` instances. Playback requested through `AudioCueEventChannelSO` events, not direct calls. - -### Save/Load -`SaveLoadSystem` SO + `FileManager` (JSON to disk). Auto-saves every 30s and on app pause. - -## Key Patterns -- **SO event channels** for decoupled communication (never call systems directly) -- **Object pooling** via `PoolSO` / `IFactory` (used for audio emitters) -- **NetworkBehaviour** lifecycle: `Spawned()`, `FixedUpdateNetwork()`, `Render()` -- **Singleton** with destroy-duplicate guard -- **Deterministic** logic for cross-client consistency (seeded RNG for role/mask assignment) - -## Directory Layout - -``` -Assets/ -├── 00. Scenes/ # Lobby, WaitingRoom, GameScene -├── 01. Scripts/ # Runtime C# organized by feature -│ ├── Network/ # Fusion networking layer -│ ├── MaskNPC/ # NPC behaviors (+ Local/ offline variants) -│ ├── Game/ # PlayerState, StatsManager -│ ├── Events/ # Event channel SO definitions -│ ├── Audios/ # Audio management -│ ├── UI/ # Game/lobby UI controllers -│ └── ... # Player, Input, Settings, SaveLoad, Pool, etc. -├── 02. ScriptableObjects/ # Configuration assets (events, audio, settings) -├── 03. Prefabs/ # Reusable GameObjects -├── 04. Audios/ # Audio clips (Music/, SFX/) -└── 05. Arts/ # Visual assets, animations, models -``` - -## Key Packages - -- `com.unity.inputsystem` (1.17.0) — New Input System -- `com.unity.cinemachine` (2.10.5) — Camera -- `com.unity.ai.navigation` (2.0.9) — NavMesh -- `com.unity.render-pipelines.universal` (17.3.0) — URP -- `com.unity.visualeffectgraph` (17.0.4) — VFX diff --git a/GGJ26/.claude/rules/unity-gotchas.md b/GGJ26/.claude/rules/unity-gotchas.md deleted file mode 100644 index 34901e6c..00000000 --- a/GGJ26/.claude/rules/unity-gotchas.md +++ /dev/null @@ -1,29 +0,0 @@ -# Unity-Specific Rules - -## Meta Files -- Every asset and folder has a `.meta` file. Never delete, move, or rename a file without its `.meta` pair. -- If you create a new file, Unity auto-generates the `.meta`. Do not manually create `.meta` files. - -## Hands-Off Directories -- Never edit files in `Library/`, `Temp/`, `Logs/`, or `obj/`. These are generated by Unity. -- These directories are git-ignored and should stay that way. - -## Script Compilation Verification -- After creating or modifying any C# script, always run `read_console` to check for compilation errors before proceeding. -- Unity must finish domain reload (compilation) before new types/components can be used. Poll `editor_state` resource's `isCompiling` field if needed. - -## Prefab and Scene Files -- `.prefab`, `.unity`, and `.asset` files are binary-ish YAML serialized by Unity. -- Use Unity MCP tools (`manage_gameobject`, `manage_components`, `manage_scene`, etc.) to modify them — do not raw-edit these files with text tools. -- Exception: ScriptableObject `.asset` files with simple fields can sometimes be safely text-edited, but prefer MCP tools. - -## NetworkBehaviour Lifecycle -- Fusion `NetworkBehaviour` uses `Spawned()` (not `Start()`), `FixedUpdateNetwork()` (not `Update()`), and `Render()` for visual interpolation. -- `[Networked]` properties require `{ get; set; }` auto-property syntax with a `default` keyword. -- Always check `Object.HasStateAuthority` or `Object.HasInputAuthority` before writing networked state. - -## Common Pitfalls -- `SerializeField` fields must not be `readonly` — Unity cannot serialize them. -- Coroutines (`StartCoroutine`) do not work in `NetworkBehaviour.FixedUpdateNetwork()` — use Fusion timers (`TickTimer`) instead. -- When adding components via MCP, the script must have compiled successfully first. -- NavMeshAgent requires a baked NavMesh in the scene to function. diff --git a/GGJ26/.claude/skills/unity-mcp-skill/SKILL.md b/GGJ26/.claude/skills/unity-mcp-skill/SKILL.md deleted file mode 100644 index 0cfd0399..00000000 --- a/GGJ26/.claude/skills/unity-mcp-skill/SKILL.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -name: unity-mcp-orchestrator -description: Orchestrate Unity Editor via MCP (Model Context Protocol) tools and resources. Use when working with Unity projects through MCP for Unity - creating/modifying GameObjects, editing scripts, managing scenes, running tests, or any Unity Editor automation. Provides best practices, tool schemas, and workflow patterns for effective Unity-MCP integration. ---- - -# Unity-MCP Operator Guide - -This skill helps you effectively use the Unity Editor with MCP tools and resources. - -## Quick Start: Resource-First Workflow - -**Always read relevant resources before using tools.** This prevents errors and provides the necessary context. - -``` -1. Check editor state → mcpforunity://editor/state -2. Understand the scene → mcpforunity://scene/gameobject-api -3. Find what you need → find_gameobjects or resources -4. Take action → tools (manage_gameobject, create_script, script_apply_edits, apply_text_edits, validate_script, delete_script, get_sha, etc.) -5. Verify results → read_console, capture_screenshot (in manage_scene), resources -``` - -## Critical Best Practices - -### 1. After Writing/Editing Scripts: Always Refresh and Check Console - -```python -# After create_script or script_apply_edits: -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) -read_console(types=["error"], count=10, include_stacktrace=True) -``` - -**Why:** Unity must compile scripts before they're usable. Compilation errors block all tool execution. - -### 2. Use `batch_execute` for Multiple Operations - -```python -# 10-100x faster than sequential calls -batch_execute( - commands=[ - {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube1", "primitive_type": "Cube"}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube2", "primitive_type": "Cube"}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "Cube3", "primitive_type": "Cube"}} - ], - parallel=True # Read-only operations can run in parallel -) -``` - -**Max 25 commands per batch.** Use `fail_fast=True` for dependent operations. - -### 3. Use `screenshot` in manage_scene to Verify Visual Results - -```python -# Via manage_scene -manage_scene(action="screenshot") # Returns base64 image - -# After creating/modifying objects, verify visually: -# 1. Create objects -# 2. capture screenshot -# 3. Analyze if result matches intent -``` - -### 4. Check Console After Major Changes - -```python -read_console( - action="get", - types=["error", "warning"], # Focus on problems - count=10, - format="detailed" -) -``` - -### 5. Always Check `editor_state` Before Complex Operations - -```python -# Read mcpforunity://editor/state to check: -# - is_compiling: Wait if true -# - is_domain_reload_pending: Wait if true -# - ready_for_tools: Only proceed if true -# - blocking_reasons: Why tools might fail -``` - -## Parameter Type Conventions - -### Vectors (position, rotation, scale, color) -```python -# Both forms accepted: -position=[1.0, 2.0, 3.0] # List -position="[1.0, 2.0, 3.0]" # JSON string -``` - -### Booleans -```python -# Both forms accepted: -include_inactive=True # Boolean -include_inactive="true" # String -``` - -### Colors -```python -# Auto-detected format: -color=[255, 0, 0, 255] # 0-255 range -color=[1.0, 0.0, 0.0, 1.0] # 0.0-1.0 normalized (auto-converted) -``` - -### Paths -```python -# Assets-relative (default): -path="Assets/Scripts/MyScript.cs" - -# URI forms: -uri="mcpforunity://path/Assets/Scripts/MyScript.cs" -uri="file:///full/path/to/file.cs" -``` - -## Core Tool Categories - -| Category | Key Tools | Use For | -|----------|-----------|---------| -| **Scene** | `manage_scene`, `find_gameobjects` | Scene operations, finding objects | -| **Objects** | `manage_gameobject`, `manage_components` | Creating/modifying GameObjects | -| **Scripts** | `create_script`, `script_apply_edits`, `refresh_unity` | C# code management | -| **Assets** | `manage_asset`, `manage_prefabs` | Asset operations | -| **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control | -| **Testing** | `run_tests`, `get_test_job` | Unity Test Framework | -| **Batch** | `batch_execute` | Parallel/bulk operations | - -## Common Workflows - -### Creating a New Script and Using It - -```python -# 1. Create the script -create_script( - path="Assets/Scripts/PlayerController.cs", - contents="using UnityEngine;\n\npublic class PlayerController : MonoBehaviour\n{\n void Update() { }\n}" -) - -# 2. CRITICAL: Refresh and wait for compilation -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) - -# 3. Check for compilation errors -read_console(types=["error"], count=10) - -# 4. Only then attach to GameObject -manage_gameobject(action="modify", target="Player", components_to_add=["PlayerController"]) -``` - -### Finding and Modifying GameObjects - -```python -# 1. Find by name/tag/component (returns IDs only) -result = find_gameobjects(search_term="Enemy", search_method="by_tag", page_size=50) - -# 2. Get full data via resource -# mcpforunity://scene/gameobject/{instance_id} - -# 3. Modify using the ID -manage_gameobject(action="modify", target=instance_id, position=[10, 0, 0]) -``` - -### Running and Monitoring Tests - -```python -# 1. Start test run (async) -result = run_tests(mode="EditMode", test_names=["MyTests.TestSomething"]) -job_id = result["job_id"] - -# 2. Poll for completion -result = get_test_job(job_id=job_id, wait_timeout=60, include_failed_tests=True) -``` - -## Pagination Pattern - -Large queries return paginated results. Always follow `next_cursor`: - -```python -cursor = 0 -all_items = [] -while True: - result = manage_scene(action="get_hierarchy", page_size=50, cursor=cursor) - all_items.extend(result["data"]["items"]) - if not result["data"].get("next_cursor"): - break - cursor = result["data"]["next_cursor"] -``` - -## Multi-Instance Workflow - -When multiple Unity Editors are running: - -```python -# 1. List instances via resource: mcpforunity://instances -# 2. Set active instance -set_active_instance(instance="MyProject@abc123") -# 3. All subsequent calls route to that instance -``` - -## Error Recovery - -| Symptom | Cause | Solution | -|---------|-------|----------| -| Tools return "busy" | Compilation in progress | Wait, check `editor_state` | -| "stale_file" error | File changed since SHA | Re-fetch SHA with `get_sha`, retry | -| Connection lost | Domain reload | Wait ~5s, reconnect | -| Commands fail silently | Wrong instance | Check `set_active_instance` | - -## Reference Files - -For detailed schemas and examples: - -- **[tools-reference.md](references/tools-reference.md)**: Complete tool documentation with all parameters -- **[resources-reference.md](references/resources-reference.md)**: All available resources and their data -- **[workflows.md](references/workflows.md)**: Extended workflow examples and patterns diff --git a/GGJ26/.claude/skills/unity-mcp-skill/references/resources-reference.md b/GGJ26/.claude/skills/unity-mcp-skill/references/resources-reference.md deleted file mode 100644 index 35cfca9c..00000000 --- a/GGJ26/.claude/skills/unity-mcp-skill/references/resources-reference.md +++ /dev/null @@ -1,493 +0,0 @@ -# Unity-MCP Resources Reference - -Resources provide read-only access to Unity state. Use resources to inspect before using tools to modify. - -## Table of Contents - -- [Editor State Resources](#editor-state-resources) -- [Scene & GameObject Resources](#scene--gameobject-resources) -- [Prefab Resources](#prefab-resources) -- [Project Resources](#project-resources) -- [Instance Resources](#instance-resources) -- [Test Resources](#test-resources) - ---- - -## URI Scheme - -All resources use `mcpforunity://` scheme: - -``` -mcpforunity://{category}/{resource_path}[?query_params] -``` - -**Categories:** `editor`, `scene`, `prefab`, `project`, `menu-items`, `custom-tools`, `tests`, `instances` - ---- - -## Editor State Resources - -### mcpforunity://editor/state - -**Purpose:** Editor readiness snapshot - check before tool operations. - -**Returns:** -```json -{ - "unity_version": "2022.3.10f1", - "is_compiling": false, - "is_domain_reload_pending": false, - "play_mode": { - "is_playing": false, - "is_paused": false - }, - "active_scene": { - "path": "Assets/Scenes/Main.unity", - "name": "Main" - }, - "ready_for_tools": true, - "blocking_reasons": [], - "recommended_retry_after_ms": null, - "staleness": { - "age_ms": 150, - "is_stale": false - } -} -``` - -**Key Fields:** -- `ready_for_tools`: Only proceed if `true` -- `is_compiling`: Wait if `true` -- `blocking_reasons`: Array explaining why tools might fail -- `recommended_retry_after_ms`: Suggested wait time - -### mcpforunity://editor/selection - -**Purpose:** Currently selected objects. - -**Returns:** -```json -{ - "activeObject": "Player", - "activeGameObject": "Player", - "activeInstanceID": 12345, - "count": 3, - "gameObjects": ["Player", "Enemy", "Wall"], - "assetGUIDs": [] -} -``` - -### mcpforunity://editor/active-tool - -**Purpose:** Current editor tool state. - -**Returns:** -```json -{ - "activeTool": "Move", - "isCustom": false, - "pivotMode": "Center", - "pivotRotation": "Global" -} -``` - -### mcpforunity://editor/windows - -**Purpose:** All open editor windows. - -**Returns:** -```json -{ - "windows": [ - { - "title": "Scene", - "typeName": "UnityEditor.SceneView", - "isFocused": true, - "position": {"x": 0, "y": 0, "width": 800, "height": 600} - } - ] -} -``` - -### mcpforunity://editor/prefab-stage - -**Purpose:** Current prefab editing context. - -**Returns:** -```json -{ - "isOpen": true, - "assetPath": "Assets/Prefabs/Player.prefab", - "prefabRootName": "Player", - "isDirty": false -} -``` - ---- - -## Scene & GameObject Resources - -### mcpforunity://scene/gameobject-api - -**Purpose:** Documentation for GameObject resources (read this first). - -### mcpforunity://scene/gameobject/{instance_id} - -**Purpose:** Basic GameObject data (metadata, no component properties). - -**Parameters:** -- `instance_id` (int): GameObject instance ID from `find_gameobjects` - -**Returns:** -```json -{ - "instanceID": 12345, - "name": "Player", - "tag": "Player", - "layer": 8, - "layerName": "Player", - "active": true, - "activeInHierarchy": true, - "isStatic": false, - "transform": { - "position": [0, 1, 0], - "rotation": [0, 0, 0], - "scale": [1, 1, 1] - }, - "parent": {"instanceID": 0}, - "children": [{"instanceID": 67890}], - "componentTypes": ["Transform", "Rigidbody", "PlayerController"], - "path": "/Player" -} -``` - -### mcpforunity://scene/gameobject/{instance_id}/components - -**Purpose:** All components with full property serialization (paginated). - -**Parameters:** -- `instance_id` (int): GameObject instance ID -- `page_size` (int): Default 25, max 100 -- `cursor` (int): Pagination cursor -- `include_properties` (bool): Default true, set false for just types - -**Returns:** -```json -{ - "gameObjectID": 12345, - "gameObjectName": "Player", - "components": [ - { - "type": "Transform", - "properties": { - "position": {"x": 0, "y": 1, "z": 0}, - "rotation": {"x": 0, "y": 0, "z": 0, "w": 1} - } - }, - { - "type": "Rigidbody", - "properties": { - "mass": 1.0, - "useGravity": true - } - } - ], - "cursor": 0, - "pageSize": 25, - "nextCursor": null, - "hasMore": false -} -``` - -### mcpforunity://scene/gameobject/{instance_id}/component/{component_name} - -**Purpose:** Single component with full properties. - -**Parameters:** -- `instance_id` (int): GameObject instance ID -- `component_name` (string): e.g., "Rigidbody", "Camera", "Transform" - -**Returns:** -```json -{ - "gameObjectID": 12345, - "gameObjectName": "Player", - "component": { - "type": "Rigidbody", - "properties": { - "mass": 1.0, - "drag": 0, - "angularDrag": 0.05, - "useGravity": true, - "isKinematic": false - } - } -} -``` - ---- - -## Prefab Resources - -### mcpforunity://prefab-api - -**Purpose:** Documentation for prefab resources. - -### mcpforunity://prefab/{encoded_path} - -**Purpose:** Prefab asset information. - -**Parameters:** -- `encoded_path` (string): URL-encoded path, e.g., `Assets%2FPrefabs%2FPlayer.prefab` - -**Path Encoding:** -``` -Assets/Prefabs/Player.prefab → Assets%2FPrefabs%2FPlayer.prefab -``` - -**Returns:** -```json -{ - "assetPath": "Assets/Prefabs/Player.prefab", - "guid": "abc123...", - "prefabType": "Regular", - "rootObjectName": "Player", - "rootComponentTypes": ["Transform", "PlayerController"], - "childCount": 5, - "isVariant": false, - "parentPrefab": null -} -``` - -### mcpforunity://prefab/{encoded_path}/hierarchy - -**Purpose:** Full prefab hierarchy with nested prefab info. - -**Returns:** -```json -{ - "prefabPath": "Assets/Prefabs/Player.prefab", - "total": 6, - "items": [ - { - "name": "Player", - "instanceId": 12345, - "path": "/Player", - "activeSelf": true, - "childCount": 2, - "componentTypes": ["Transform", "PlayerController"] - }, - { - "name": "Model", - "path": "/Player/Model", - "isNestedPrefab": true, - "nestedPrefabPath": "Assets/Prefabs/PlayerModel.prefab" - } - ] -} -``` - ---- - -## Project Resources - -### mcpforunity://project/info - -**Purpose:** Static project configuration. - -**Returns:** -```json -{ - "projectRoot": "/Users/dev/MyProject", - "projectName": "MyProject", - "unityVersion": "2022.3.10f1", - "platform": "StandaloneWindows64", - "assetsPath": "/Users/dev/MyProject/Assets" -} -``` - -### mcpforunity://project/tags - -**Purpose:** All tags defined in TagManager. - -**Returns:** -```json -["Untagged", "Respawn", "Finish", "EditorOnly", "MainCamera", "Player", "GameController", "Enemy"] -``` - -### mcpforunity://project/layers - -**Purpose:** All layers with indices (0-31). - -**Returns:** -```json -{ - "0": "Default", - "1": "TransparentFX", - "2": "Ignore Raycast", - "4": "Water", - "5": "UI", - "8": "Player", - "9": "Enemy" -} -``` - -### mcpforunity://menu-items - -**Purpose:** All available Unity menu items. - -**Returns:** -```json -[ - "File/New Scene", - "File/Open Scene", - "File/Save", - "Edit/Undo", - "Edit/Redo", - "GameObject/Create Empty", - "GameObject/3D Object/Cube", - "Window/General/Console" -] -``` - -### mcpforunity://custom-tools - -**Purpose:** Custom tools available in the active Unity project. - -**Returns:** -```json -{ - "project_id": "MyProject", - "tool_count": 3, - "tools": [ - { - "name": "capture_screenshot", - "description": "Capture screenshots in Unity", - "parameters": [ - {"name": "filename", "type": "string", "required": true}, - {"name": "width", "type": "int", "required": false}, - {"name": "height", "type": "int", "required": false} - ] - } - ] -} -``` - ---- - -## Instance Resources - -### mcpforunity://instances - -**Purpose:** All running Unity Editor instances (for multi-instance workflows). - -**Returns:** -```json -{ - "transport": "http", - "instance_count": 2, - "instances": [ - { - "id": "MyProject@abc123", - "name": "MyProject", - "hash": "abc123", - "unity_version": "2022.3.10f1", - "connected_at": "2024-01-15T10:30:00Z" - }, - { - "id": "TestProject@def456", - "name": "TestProject", - "hash": "def456", - "unity_version": "2022.3.10f1", - "connected_at": "2024-01-15T11:00:00Z" - } - ], - "warnings": [] -} -``` - -**Use with:** `set_active_instance(instance="MyProject@abc123")` - ---- - -## Test Resources - -### mcpforunity://tests - -**Purpose:** All tests in the project. - -**Returns:** -```json -[ - { - "name": "TestSomething", - "full_name": "MyTests.TestSomething", - "mode": "EditMode" - }, - { - "name": "TestOther", - "full_name": "MyTests.TestOther", - "mode": "PlayMode" - } -] -``` - -### mcpforunity://tests/{mode} - -**Purpose:** Tests filtered by mode. - -**Parameters:** -- `mode` (string): "EditMode" or "PlayMode" - -**Example:** `mcpforunity://tests/EditMode` - ---- - -## Best Practices - -### 1. Check Editor State First - -```python -# Before any complex operation: -# Read mcpforunity://editor/state -# Check ready_for_tools == true -``` - -### 2. Use Find Then Read Pattern - -```python -# 1. find_gameobjects to get IDs -result = find_gameobjects(search_term="Player") - -# 2. Read resource for full data -# mcpforunity://scene/gameobject/{id} -``` - -### 3. Paginate Large Queries - -```python -# Start with include_properties=false for component lists -# mcpforunity://scene/gameobject/{id}/components?include_properties=false&page_size=25 - -# Then read specific components as needed -# mcpforunity://scene/gameobject/{id}/component/Rigidbody -``` - -### 4. URL-Encode Prefab Paths - -```python -# Wrong: -# mcpforunity://prefab/Assets/Prefabs/Player.prefab - -# Correct: -# mcpforunity://prefab/Assets%2FPrefabs%2FPlayer.prefab -``` - -### 5. Multi-Instance Awareness - -```python -# Always check mcpforunity://instances when: -# - First connecting -# - Commands fail unexpectedly -# - Working with multiple projects -``` diff --git a/GGJ26/.claude/skills/unity-mcp-skill/references/tools-reference.md b/GGJ26/.claude/skills/unity-mcp-skill/references/tools-reference.md deleted file mode 100644 index fbc8b828..00000000 --- a/GGJ26/.claude/skills/unity-mcp-skill/references/tools-reference.md +++ /dev/null @@ -1,606 +0,0 @@ -# Unity-MCP Tools Reference - -Complete reference for all MCP tools. Each tool includes parameters, types, and usage examples. - -## Table of Contents - -- [Infrastructure Tools](#infrastructure-tools) -- [Scene Tools](#scene-tools) -- [GameObject Tools](#gameobject-tools) -- [Script Tools](#script-tools) -- [Asset Tools](#asset-tools) -- [Material & Shader Tools](#material--shader-tools) -- [Editor Control Tools](#editor-control-tools) -- [Testing Tools](#testing-tools) - ---- - -## Infrastructure Tools - -### batch_execute - -Execute multiple MCP commands in a single batch (10-100x faster). - -```python -batch_execute( - commands=[ # list[dict], required, max 25 - {"tool": "tool_name", "params": {...}}, - ... - ], - parallel=False, # bool, optional - run read-only ops in parallel - fail_fast=False, # bool, optional - stop on first failure - max_parallelism=None # int, optional - max parallel workers -) -``` - -### set_active_instance - -Route commands to a specific Unity instance (multi-instance workflows). - -```python -set_active_instance( - instance="ProjectName@abc123" # str, required - Name@hash or hash prefix -) -``` - -### refresh_unity - -Refresh asset database and trigger script compilation. - -```python -refresh_unity( - mode="if_dirty", # "if_dirty" | "force" - scope="all", # "assets" | "scripts" | "all" - compile="none", # "none" | "request" - wait_for_ready=True # bool - wait until editor ready -) -``` - ---- - -## Scene Tools - -### manage_scene - -Scene CRUD operations and hierarchy queries. - -```python -# Get hierarchy (paginated) -manage_scene( - action="get_hierarchy", - page_size=50, # int, default 50, max 500 - cursor=0, # int, pagination cursor - parent=None, # str|int, optional - filter by parent - include_transform=False # bool - include local transforms -) - -# Screenshot -manage_scene(action="screenshot") # Returns base64 PNG - -# Other actions -manage_scene(action="get_active") # Current scene info -manage_scene(action="get_build_settings") # Build settings -manage_scene(action="create", name="NewScene", path="Assets/Scenes/") -manage_scene(action="load", path="Assets/Scenes/Main.unity") -manage_scene(action="save") -``` - -### find_gameobjects - -Search for GameObjects (returns instance IDs only). - -```python -find_gameobjects( - search_term="Player", # str, required - search_method="by_name", # "by_name"|"by_tag"|"by_layer"|"by_component"|"by_path"|"by_id" - include_inactive=False, # bool|str - page_size=50, # int, default 50, max 500 - cursor=0 # int, pagination cursor -) -# Returns: {"ids": [12345, 67890], "next_cursor": 50, ...} -``` - ---- - -## GameObject Tools - -### manage_gameobject - -Create, modify, delete, duplicate GameObjects. - -```python -# Create -manage_gameobject( - action="create", - name="MyCube", # str, required - primitive_type="Cube", # "Cube"|"Sphere"|"Capsule"|"Cylinder"|"Plane"|"Quad" - position=[0, 1, 0], # list[float] or JSON string "[0,1,0]" - rotation=[0, 45, 0], # euler angles - scale=[1, 1, 1], - components_to_add=["Rigidbody", "BoxCollider"], - save_as_prefab=False, - prefab_path="Assets/Prefabs/MyCube.prefab" -) - -# Modify -manage_gameobject( - action="modify", - target="Player", # name, path, or instance ID - search_method="by_name", # how to find target - position=[10, 0, 0], - rotation=[0, 90, 0], - scale=[2, 2, 2], - set_active=True, - layer="Player", - components_to_add=["AudioSource"], - components_to_remove=["OldComponent"], - component_properties={ # nested dict for property setting - "Rigidbody": { - "mass": 10.0, - "useGravity": True - } - } -) - -# Delete -manage_gameobject(action="delete", target="OldObject") - -# Duplicate -manage_gameobject( - action="duplicate", - target="Player", - new_name="Player2", - offset=[5, 0, 0] # position offset from original -) - -# Move relative -manage_gameobject( - action="move_relative", - target="Player", - reference_object="Enemy", # optional reference - direction="left", # "left"|"right"|"up"|"down"|"forward"|"back" - distance=5.0, - world_space=True -) -``` - -### manage_components - -Add, remove, or set properties on components. - -```python -# Add component -manage_components( - action="add", - target=12345, # instance ID (preferred) or name - component_type="Rigidbody", - search_method="by_id" -) - -# Remove component -manage_components( - action="remove", - target="Player", - component_type="OldScript" -) - -# Set single property -manage_components( - action="set_property", - target=12345, - component_type="Rigidbody", - property="mass", - value=5.0 -) - -# Set multiple properties -manage_components( - action="set_property", - target=12345, - component_type="Transform", - properties={ - "position": [1, 2, 3], - "localScale": [2, 2, 2] - } -) -``` - ---- - -## Script Tools - -### create_script - -Create a new C# script. - -```python -create_script( - path="Assets/Scripts/MyScript.cs", # str, required - contents='''using UnityEngine; - -public class MyScript : MonoBehaviour -{ - void Start() { } - void Update() { } -}''', - script_type="MonoBehaviour", # optional hint - namespace="MyGame" # optional namespace -) -``` - -### script_apply_edits - -Apply structured edits to C# scripts (safer than raw text edits). - -```python -script_apply_edits( - name="MyScript", # script name (no .cs) - path="Assets/Scripts", # folder path - edits=[ - # Replace entire method - { - "op": "replace_method", - "methodName": "Update", - "replacement": "void Update() { transform.Rotate(Vector3.up); }" - }, - # Insert new method - { - "op": "insert_method", - "afterMethod": "Start", - "code": "void OnEnable() { Debug.Log(\"Enabled\"); }" - }, - # Delete method - { - "op": "delete_method", - "methodName": "OldMethod" - }, - # Anchor-based insert - { - "op": "anchor_insert", - "anchor": "void Start()", - "position": "before", # "before" | "after" - "text": "// Called before Start\n" - }, - # Regex replace - { - "op": "regex_replace", - "pattern": "Debug\\.Log\\(", - "text": "Debug.LogWarning(" - }, - # Prepend/append to file - {"op": "prepend", "text": "// File header\n"}, - {"op": "append", "text": "\n// File footer"} - ] -) -``` - -### apply_text_edits - -Apply precise character-position edits (1-indexed lines/columns). - -```python -apply_text_edits( - uri="mcpforunity://path/Assets/Scripts/MyScript.cs", - edits=[ - { - "startLine": 10, - "startCol": 5, - "endLine": 10, - "endCol": 20, - "newText": "replacement text" - } - ], - precondition_sha256="abc123...", # optional, prevents stale edits - strict=True # optional, stricter validation -) -``` - -### validate_script - -Check script for syntax/semantic errors. - -```python -validate_script( - uri="mcpforunity://path/Assets/Scripts/MyScript.cs", - level="standard", # "basic" | "standard" - include_diagnostics=True # include full error details -) -``` - -### get_sha - -Get file hash without content (for preconditions). - -```python -get_sha(uri="mcpforunity://path/Assets/Scripts/MyScript.cs") -# Returns: {"sha256": "...", "lengthBytes": 1234, "lastModifiedUtc": "..."} -``` - -### delete_script - -Delete a script file. - -```python -delete_script(uri="mcpforunity://path/Assets/Scripts/OldScript.cs") -``` - ---- - -## Asset Tools - -### manage_asset - -Asset operations: search, import, create, modify, delete. - -```python -# Search assets (paginated) -manage_asset( - action="search", - path="Assets", # search scope - search_pattern="*.prefab", # glob or "t:MonoScript" filter - filter_type="Prefab", # optional type filter - page_size=25, # keep small to avoid large payloads - page_number=1, # 1-based - generate_preview=False # avoid base64 bloat -) - -# Get asset info -manage_asset(action="get_info", path="Assets/Prefabs/Player.prefab") - -# Create asset -manage_asset( - action="create", - path="Assets/Materials/NewMaterial.mat", - asset_type="Material", - properties={"color": [1, 0, 0, 1]} -) - -# Duplicate/move/rename -manage_asset(action="duplicate", path="Assets/A.prefab", destination="Assets/B.prefab") -manage_asset(action="move", path="Assets/A.prefab", destination="Assets/Prefabs/A.prefab") -manage_asset(action="rename", path="Assets/A.prefab", destination="Assets/B.prefab") - -# Create folder -manage_asset(action="create_folder", path="Assets/NewFolder") - -# Delete -manage_asset(action="delete", path="Assets/OldAsset.asset") -``` - -### manage_prefabs - -Headless prefab operations. - -```python -# Get prefab info -manage_prefabs(action="get_info", prefab_path="Assets/Prefabs/Player.prefab") - -# Get prefab hierarchy -manage_prefabs(action="get_hierarchy", prefab_path="Assets/Prefabs/Player.prefab") - -# Create prefab from scene GameObject -manage_prefabs( - action="create_from_gameobject", - target="Player", # GameObject in scene - prefab_path="Assets/Prefabs/Player.prefab", - allow_overwrite=False -) - -# Modify prefab contents (headless) -manage_prefabs( - action="modify_contents", - prefab_path="Assets/Prefabs/Player.prefab", - target="ChildObject", # object within prefab - position=[0, 1, 0], - components_to_add=["AudioSource"] -) -``` - ---- - -## Material & Shader Tools - -### manage_material - -Create and modify materials. - -```python -# Create material -manage_material( - action="create", - material_path="Assets/Materials/Red.mat", - shader="Standard", - properties={"_Color": [1, 0, 0, 1]} -) - -# Get material info -manage_material(action="get_material_info", material_path="Assets/Materials/Red.mat") - -# Set shader property -manage_material( - action="set_material_shader_property", - material_path="Assets/Materials/Red.mat", - property="_Metallic", - value=0.8 -) - -# Set color -manage_material( - action="set_material_color", - material_path="Assets/Materials/Red.mat", - property="_BaseColor", - color=[0, 1, 0, 1] # RGBA -) - -# Assign to renderer -manage_material( - action="assign_material_to_renderer", - target="MyCube", - material_path="Assets/Materials/Red.mat", - slot=0 # material slot index -) - -# Set renderer color directly -manage_material( - action="set_renderer_color", - target="MyCube", - color=[1, 0, 0, 1], - mode="instance" # "shared"|"instance"|"property_block" -) -``` - -### manage_texture - -Create procedural textures. - -```python -manage_texture( - action="create", - path="Assets/Textures/Checker.png", - width=64, - height=64, - fill_color=[255, 255, 255, 255] # or [1.0, 1.0, 1.0, 1.0] -) - -# Apply pattern -manage_texture( - action="apply_pattern", - path="Assets/Textures/Checker.png", - pattern="checkerboard", # "checkerboard"|"stripes"|"dots"|"grid"|"brick" - palette=[[0,0,0,255], [255,255,255,255]], - pattern_size=8 -) - -# Apply gradient -manage_texture( - action="apply_gradient", - path="Assets/Textures/Gradient.png", - gradient_type="linear", # "linear"|"radial" - gradient_angle=45, - palette=[[255,0,0,255], [0,0,255,255]] -) -``` - ---- - -## Editor Control Tools - -### manage_editor - -Control Unity Editor state. - -```python -manage_editor(action="play") # Enter play mode -manage_editor(action="pause") # Pause play mode -manage_editor(action="stop") # Exit play mode - -manage_editor(action="set_active_tool", tool_name="Move") # Move/Rotate/Scale/etc. - -manage_editor(action="add_tag", tag_name="Enemy") -manage_editor(action="remove_tag", tag_name="OldTag") - -manage_editor(action="add_layer", layer_name="Projectiles") -manage_editor(action="remove_layer", layer_name="OldLayer") -``` - -### execute_menu_item - -Execute any Unity menu item. - -```python -execute_menu_item(menu_path="File/Save Project") -execute_menu_item(menu_path="GameObject/3D Object/Cube") -execute_menu_item(menu_path="Window/General/Console") -``` - -### read_console - -Read or clear Unity console messages. - -```python -# Get recent messages -read_console( - action="get", - types=["error", "warning", "log"], # or ["all"] - count=10, # max messages (ignored with paging) - filter_text="NullReference", # optional text filter - since_timestamp="2024-01-01T00:00:00Z", # optional time filter - page_size=50, - cursor=0, - format="detailed", # "plain"|"detailed"|"json" - include_stacktrace=True -) - -# Clear console -read_console(action="clear") -``` - ---- - -## Testing Tools - -### run_tests - -Start async test execution. - -```python -result = run_tests( - mode="EditMode", # "EditMode"|"PlayMode" - test_names=["MyTests.TestA", "MyTests.TestB"], # specific tests - group_names=["Integration*"], # regex patterns - category_names=["Unit"], # NUnit categories - assembly_names=["Tests"], # assembly filter - include_failed_tests=True, # include failure details - include_details=False # include all test details -) -# Returns: {"job_id": "abc123", ...} -``` - -### get_test_job - -Poll test job status. - -```python -result = get_test_job( - job_id="abc123", - wait_timeout=60, # wait up to N seconds - include_failed_tests=True, - include_details=False -) -# Returns: {"status": "complete"|"running"|"failed", "results": {...}} -``` - ---- - -## Search Tools - -### find_in_file - -Search file contents with regex. - -```python -find_in_file( - uri="mcpforunity://path/Assets/Scripts/MyScript.cs", - pattern="public void \\w+", # regex pattern - max_results=200, - ignore_case=True -) -# Returns: line numbers, content excerpts, match positions -``` - ---- - -## Custom Tools - -### execute_custom_tool - -Execute project-specific custom tools. - -```python -execute_custom_tool( - tool_name="my_custom_tool", - parameters={"param1": "value", "param2": 42} -) -``` - -Discover available custom tools via `mcpforunity://custom-tools` resource. diff --git a/GGJ26/.claude/skills/unity-mcp-skill/references/workflows.md b/GGJ26/.claude/skills/unity-mcp-skill/references/workflows.md deleted file mode 100644 index 7108e78b..00000000 --- a/GGJ26/.claude/skills/unity-mcp-skill/references/workflows.md +++ /dev/null @@ -1,609 +0,0 @@ -# Unity-MCP Workflow Patterns - -Common workflows and patterns for effective Unity-MCP usage. - -## Table of Contents - -- [Setup & Verification](#setup--verification) -- [Scene Creation Workflows](#scene-creation-workflows) -- [Script Development Workflows](#script-development-workflows) -- [Asset Management Workflows](#asset-management-workflows) -- [Testing Workflows](#testing-workflows) -- [Debugging Workflows](#debugging-workflows) -- [Batch Operations](#batch-operations) - ---- - -## Setup & Verification - -### Initial Connection Verification - -```python -# 1. Check editor state -# Read mcpforunity://editor/state - -# 2. Verify ready_for_tools == true -# If false, wait for recommended_retry_after_ms - -# 3. Check active scene -# Read mcpforunity://editor/state → active_scene - -# 4. List available instances (multi-instance) -# Read mcpforunity://instances -``` - -### Before Any Operation - -```python -# Quick readiness check pattern: -editor_state = read_resource("mcpforunity://editor/state") - -if not editor_state["ready_for_tools"]: - # Check blocking_reasons - # Wait recommended_retry_after_ms - pass - -if editor_state["is_compiling"]: - # Wait for compilation to complete - pass -``` - ---- - -## Scene Creation Workflows - -### Create Complete Scene from Scratch - -```python -# 1. Create new scene -manage_scene(action="create", name="GameLevel", path="Assets/Scenes/") - -# 2. Batch create environment objects -batch_execute(commands=[ - {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Ground", "primitive_type": "Plane", - "position": [0, 0, 0], "scale": [10, 1, 10] - }}, - {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Light", "primitive_type": "Cube" - }}, - {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Player", "primitive_type": "Capsule", - "position": [0, 1, 0] - }} -]) - -# 3. Add light component (delete cube mesh, add light) -manage_components(action="remove", target="Light", component_type="MeshRenderer") -manage_components(action="remove", target="Light", component_type="MeshFilter") -manage_components(action="remove", target="Light", component_type="BoxCollider") -manage_components(action="add", target="Light", component_type="Light") -manage_components(action="set_property", target="Light", component_type="Light", - property="type", value="Directional") - -# 4. Set up camera -manage_gameobject(action="modify", target="Main Camera", position=[0, 5, -10], - rotation=[30, 0, 0]) - -# 5. Verify with screenshot -manage_scene(action="screenshot") - -# 6. Save scene -manage_scene(action="save") -``` - -### Populate Scene with Grid of Objects - -```python -# Create 5x5 grid of cubes using batch -commands = [] -for x in range(5): - for z in range(5): - commands.append({ - "tool": "manage_gameobject", - "params": { - "action": "create", - "name": f"Cube_{x}_{z}", - "primitive_type": "Cube", - "position": [x * 2, 0, z * 2] - } - }) - -# Execute in batches of 25 -batch_execute(commands=commands[:25], parallel=True) -``` - -### Clone and Arrange Objects - -```python -# Find template object -result = find_gameobjects(search_term="Template", search_method="by_name") -template_id = result["ids"][0] - -# Duplicate in a line -for i in range(10): - manage_gameobject( - action="duplicate", - target=template_id, - new_name=f"Instance_{i}", - offset=[i * 2, 0, 0] - ) -``` - ---- - -## Script Development Workflows - -### Create New Script and Attach - -```python -# 1. Create script -create_script( - path="Assets/Scripts/EnemyAI.cs", - contents='''using UnityEngine; - -public class EnemyAI : MonoBehaviour -{ - public float speed = 5f; - public Transform target; - - void Update() - { - if (target != null) - { - Vector3 direction = (target.position - transform.position).normalized; - transform.position += direction * speed * Time.deltaTime; - } - } -}''' -) - -# 2. CRITICAL: Refresh and compile -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) - -# 3. Check for errors -console = read_console(types=["error"], count=10) -if console["messages"]: - # Handle compilation errors - print("Compilation errors:", console["messages"]) -else: - # 4. Attach to GameObject - manage_gameobject(action="modify", target="Enemy", components_to_add=["EnemyAI"]) - - # 5. Set component properties - manage_components( - action="set_property", - target="Enemy", - component_type="EnemyAI", - properties={ - "speed": 10.0 - } - ) -``` - -### Edit Existing Script Safely - -```python -# 1. Get current SHA -sha_info = get_sha(uri="mcpforunity://path/Assets/Scripts/PlayerController.cs") - -# 2. Find the method to edit -matches = find_in_file( - uri="mcpforunity://path/Assets/Scripts/PlayerController.cs", - pattern="void Update\\(\\)" -) - -# 3. Apply structured edit -script_apply_edits( - name="PlayerController", - path="Assets/Scripts", - edits=[{ - "op": "replace_method", - "methodName": "Update", - "replacement": '''void Update() - { - float h = Input.GetAxis("Horizontal"); - float v = Input.GetAxis("Vertical"); - transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime); - }''' - }] -) - -# 4. Validate -validate_script( - uri="mcpforunity://path/Assets/Scripts/PlayerController.cs", - level="standard" -) - -# 5. Refresh -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) - -# 6. Check console -read_console(types=["error"], count=10) -``` - -### Add Method to Existing Class - -```python -script_apply_edits( - name="GameManager", - path="Assets/Scripts", - edits=[ - { - "op": "insert_method", - "afterMethod": "Start", - "code": ''' - public void ResetGame() - { - SceneManager.LoadScene(SceneManager.GetActiveScene().name); - }''' - }, - { - "op": "anchor_insert", - "anchor": "using UnityEngine;", - "position": "after", - "text": "\nusing UnityEngine.SceneManagement;" - } - ] -) -``` - ---- - -## Asset Management Workflows - -### Create and Apply Material - -```python -# 1. Create material -manage_material( - action="create", - material_path="Assets/Materials/PlayerMaterial.mat", - shader="Standard", - properties={ - "_Color": [0.2, 0.5, 1.0, 1.0], - "_Metallic": 0.5, - "_Glossiness": 0.8 - } -) - -# 2. Assign to renderer -manage_material( - action="assign_material_to_renderer", - target="Player", - material_path="Assets/Materials/PlayerMaterial.mat", - slot=0 -) - -# 3. Verify visually -manage_scene(action="screenshot") -``` - -### Create Procedural Texture - -```python -# 1. Create base texture -manage_texture( - action="create", - path="Assets/Textures/Checkerboard.png", - width=256, - height=256, - fill_color=[255, 255, 255, 255] -) - -# 2. Apply checkerboard pattern -manage_texture( - action="apply_pattern", - path="Assets/Textures/Checkerboard.png", - pattern="checkerboard", - palette=[[0, 0, 0, 255], [255, 255, 255, 255]], - pattern_size=32 -) - -# 3. Create material with texture -manage_material( - action="create", - material_path="Assets/Materials/CheckerMaterial.mat", - shader="Standard" -) - -# 4. Assign texture to material (via manage_material set_material_shader_property) -``` - -### Organize Assets into Folders - -```python -# 1. Create folder structure -batch_execute(commands=[ - {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Prefabs"}}, - {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Materials"}}, - {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Scripts"}}, - {"tool": "manage_asset", "params": {"action": "create_folder", "path": "Assets/Textures"}} -]) - -# 2. Move existing assets -manage_asset(action="move", path="Assets/MyMaterial.mat", destination="Assets/Materials/MyMaterial.mat") -manage_asset(action="move", path="Assets/MyScript.cs", destination="Assets/Scripts/MyScript.cs") -``` - -### Search and Process Assets - -```python -# Find all prefabs -result = manage_asset( - action="search", - path="Assets", - search_pattern="*.prefab", - page_size=50, - generate_preview=False -) - -# Process each prefab -for asset in result["assets"]: - prefab_path = asset["path"] - # Get prefab info - info = manage_prefabs(action="get_info", prefab_path=prefab_path) - print(f"Prefab: {prefab_path}, Children: {info['childCount']}") -``` - ---- - -## Testing Workflows - -### Run Specific Tests - -```python -# 1. List available tests -# Read mcpforunity://tests/EditMode - -# 2. Run specific tests -result = run_tests( - mode="EditMode", - test_names=["MyTests.TestPlayerMovement", "MyTests.TestEnemySpawn"], - include_failed_tests=True -) -job_id = result["job_id"] - -# 3. Wait for results -final_result = get_test_job( - job_id=job_id, - wait_timeout=60, - include_failed_tests=True -) - -# 4. Check results -if final_result["status"] == "complete": - for test in final_result.get("failed_tests", []): - print(f"FAILED: {test['name']}: {test['message']}") -``` - -### Run Tests by Category - -```python -# Run all unit tests -result = run_tests( - mode="EditMode", - category_names=["Unit"], - include_failed_tests=True -) - -# Poll until complete -while True: - status = get_test_job(job_id=result["job_id"], wait_timeout=30) - if status["status"] in ["complete", "failed"]: - break -``` - -### Test-Driven Development Pattern - -```python -# 1. Write test first -create_script( - path="Assets/Tests/Editor/PlayerTests.cs", - contents='''using NUnit.Framework; -using UnityEngine; - -public class PlayerTests -{ - [Test] - public void TestPlayerStartsAtOrigin() - { - var player = new GameObject("TestPlayer"); - Assert.AreEqual(Vector3.zero, player.transform.position); - Object.DestroyImmediate(player); - } -}''' -) - -# 2. Refresh -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) - -# 3. Run test (expect pass for this simple test) -result = run_tests(mode="EditMode", test_names=["PlayerTests.TestPlayerStartsAtOrigin"]) -get_test_job(job_id=result["job_id"], wait_timeout=30) -``` - ---- - -## Debugging Workflows - -### Diagnose Compilation Errors - -```python -# 1. Check console for errors -errors = read_console( - types=["error"], - count=20, - include_stacktrace=True, - format="detailed" -) - -# 2. For each error, find the file and line -for error in errors["messages"]: - # Parse error message for file:line info - # Use find_in_file to locate the problematic code - pass - -# 3. After fixing, refresh and check again -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) -read_console(types=["error"], count=10) -``` - -### Investigate Missing References - -```python -# 1. Find the GameObject -result = find_gameobjects(search_term="Player", search_method="by_name") - -# 2. Get all components -# Read mcpforunity://scene/gameobject/{id}/components - -# 3. Check for null references in serialized fields -# Look for fields with null/missing values - -# 4. Find the referenced object -result = find_gameobjects(search_term="Target", search_method="by_name") - -# 5. Set the reference -manage_components( - action="set_property", - target="Player", - component_type="PlayerController", - property="target", - value={"instanceID": result["ids"][0]} # Reference by ID -) -``` - -### Check Scene State - -```python -# 1. Get hierarchy -hierarchy = manage_scene(action="get_hierarchy", page_size=100, include_transform=True) - -# 2. Find objects at unexpected positions -for item in hierarchy["data"]["items"]: - if item.get("transform", {}).get("position", [0,0,0])[1] < -100: - print(f"Object {item['name']} fell through floor!") - -# 3. Visual verification -manage_scene(action="screenshot") -``` - ---- - -## Batch Operations - -### Mass Property Update - -```python -# Find all enemies -enemies = find_gameobjects(search_term="Enemy", search_method="by_tag") - -# Update health on all enemies -commands = [] -for enemy_id in enemies["ids"]: - commands.append({ - "tool": "manage_components", - "params": { - "action": "set_property", - "target": enemy_id, - "component_type": "EnemyHealth", - "property": "maxHealth", - "value": 100 - } - }) - -# Execute in batches -for i in range(0, len(commands), 25): - batch_execute(commands=commands[i:i+25], parallel=True) -``` - -### Mass Object Creation with Variations - -```python -import random - -commands = [] -for i in range(20): - commands.append({ - "tool": "manage_gameobject", - "params": { - "action": "create", - "name": f"Tree_{i}", - "primitive_type": "Capsule", - "position": [random.uniform(-50, 50), 0, random.uniform(-50, 50)], - "scale": [1, random.uniform(2, 5), 1] - } - }) - -batch_execute(commands=commands, parallel=True) -``` - -### Cleanup Pattern - -```python -# Find all temporary objects -temps = find_gameobjects(search_term="Temp_", search_method="by_name") - -# Delete in batch -commands = [ - {"tool": "manage_gameobject", "params": {"action": "delete", "target": id}} - for id in temps["ids"] -] - -batch_execute(commands=commands, fail_fast=False) -``` - ---- - -## Error Recovery Patterns - -### Stale File Recovery - -```python -try: - apply_text_edits(uri=script_uri, edits=[...], precondition_sha256=old_sha) -except Exception as e: - if "stale_file" in str(e): - # Re-fetch SHA - new_sha = get_sha(uri=script_uri) - # Retry with new SHA - apply_text_edits(uri=script_uri, edits=[...], precondition_sha256=new_sha["sha256"]) -``` - -### Domain Reload Recovery - -```python -# After domain reload, connection may be lost -# Wait and retry pattern: -import time - -max_retries = 5 -for attempt in range(max_retries): - try: - editor_state = read_resource("mcpforunity://editor/state") - if editor_state["ready_for_tools"]: - break - except: - time.sleep(2 ** attempt) # Exponential backoff -``` - -### Compilation Block Recovery - -```python -# If tools fail due to compilation: -# 1. Check console for errors -errors = read_console(types=["error"], count=20) - -# 2. Fix the script errors -# ... edit scripts ... - -# 3. Force refresh -refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) - -# 4. Verify clean console -errors = read_console(types=["error"], count=5) -if not errors["messages"]: - # Safe to proceed with tools - pass -``` diff --git a/GGJ26/Assets/Tests.meta b/GGJ26/Assets/Tests.meta new file mode 100644 index 00000000..d3297ca9 --- /dev/null +++ b/GGJ26/Assets/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3b61b6eb47869424eafd9f91302207ef +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/GGJ26/Assets/Tests/EditMode.meta b/GGJ26/Assets/Tests/EditMode.meta new file mode 100644 index 00000000..a0372935 --- /dev/null +++ b/GGJ26/Assets/Tests/EditMode.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 64e9f8cc2cff72b40a4305c035a0f7ae +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef b/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef new file mode 100644 index 00000000..a1392498 --- /dev/null +++ b/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef @@ -0,0 +1,25 @@ +{ + "name": "GGJ26.Tests.EditMode", + "rootNamespace": "", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "UnityEngine.UI", + "Unity.TextMeshPro" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef.meta b/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef.meta new file mode 100644 index 00000000..3cff1935 --- /dev/null +++ b/GGJ26/Assets/Tests/EditMode/GGJ26.Tests.EditMode.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d4e5f6a7b8c9401d9234e5f6a7b8c901 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/GGJ26/Assets/Tests/EditMode/LobbySceneEditModeTests.cs b/GGJ26/Assets/Tests/EditMode/LobbySceneEditModeTests.cs new file mode 100644 index 00000000..297f1791 --- /dev/null +++ b/GGJ26/Assets/Tests/EditMode/LobbySceneEditModeTests.cs @@ -0,0 +1,270 @@ +using System.Linq; +using NUnit.Framework; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +[TestFixture] +public class LobbySceneEditModeTests +{ + private const string LobbyScenePath = "Assets/00. Scenes/Lobby.unity"; + + [OneTimeSetUp] + public void OpenLobbyScene() + { + EditorSceneManager.OpenScene(LobbyScenePath, OpenSceneMode.Single); + } + + // ── Helpers ────────────────────────────────────────────────── + + private static GameObject FindRequired(string name) + { + var all = Resources.FindObjectsOfTypeAll(); + var go = all.FirstOrDefault(g => g.name == name && !EditorUtility.IsPersistent(g)); + Assert.IsNotNull(go, $"GameObject '{name}' not found in Lobby scene."); + return go; + } + + private static T FindRequiredComponent(string gameObjectName) where T : Component + { + var go = FindRequired(gameObjectName); + var comp = go.GetComponent(); + Assert.IsNotNull(comp, $"Component {typeof(T).Name} not found on '{gameObjectName}'."); + return comp; + } + + // ── Core Structure ────────────────────────────────────────── + + [Test] + public void MainCamera_ExistsInScene() + { + FindRequired("Main Camera"); + } + + [Test] + public void MainCamera_HasCameraComponent() + { + FindRequiredComponent("Main Camera"); + } + + [Test] + public void DirectionalLight_ExistsInScene() + { + FindRequired("Directional Light"); + } + + [Test] + public void DirectionalLight_HasLightComponent() + { + FindRequiredComponent("Directional Light"); + } + + [Test] + public void EventSystem_ExistsInScene() + { + FindRequiredComponent("EventSystem"); + } + + [Test] + public void CanvasLobby_HasCanvasComponent() + { + FindRequiredComponent("CanvasLobby"); + } + + // ── Key Scripts ───────────────────────────────────────────── + + [Test] + public void LobbyMatchmakingUI_ExistsInScene() + { + var go = FindRequired("LobbyMatchmakingUI"); + var comp = go.GetComponent("LobbyMatchmakingUI"); + Assert.IsNotNull(comp, "LobbyMatchmakingUI component not found."); + } + + [Test] + public void FusionLauncher_ExistsInScene() + { + var go = FindRequired("FusionLauncher"); + var comp = go.GetComponent("FusionLauncher"); + Assert.IsNotNull(comp, "FusionLauncher component not found."); + } + + [Test] + public void FusionSessionFlow_ExistsInScene() + { + var go = FindRequired("FusionSessionFlow"); + var comp = go.GetComponent("FusionSessionFlow"); + Assert.IsNotNull(comp, "FusionSessionFlow component not found."); + } + + [Test] + public void AudioManager_ExistsInScene() + { + var go = FindRequired("AudioManager"); + var comp = go.GetComponent("AudioManager"); + Assert.IsNotNull(comp, "AudioManager component not found."); + } + + [Test] + public void LobbyAudioController_ExistsInScene() + { + var go = FindRequired("LobbyAudioController"); + var comp = go.GetComponent("LobbyAudioController"); + Assert.IsNotNull(comp, "LobbyAudioController component not found."); + } + + // ── Main Menu Buttons ─────────────────────────────────────── + + [Test] + [TestCase("BtnHost")] + [TestCase("BtnPublic")] + [TestCase("BtnExit")] + public void MainMenuButton_ExistsWithButtonComponent(string buttonName) + { + FindRequiredComponent