# 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) - [UI Creation Workflows](#ui-creation-workflows) - [Camera & Cinemachine Workflows](#camera--cinemachine-workflows) - [ProBuilder Workflows](#probuilder-workflows) - [Graphics & Rendering Workflows](#graphics--rendering-workflows) - [Package Management Workflows](#package-management-workflows) - [Package Deployment Workflows](#package-deployment-workflows) - [API Verification Workflows](#api-verification-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 Generator Build Workflow ### Fresh Scene Before Building **Always start a generated scene build with `manage_scene(action="create")`** to get a clean empty scene. This avoids conflicts with existing default objects (Camera, Light) that would cause "already exists" errors when the execution plan tries to create its own. ```python # Step 0: Create fresh empty scene (replaces current scene entirely) manage_scene(action="create", name="MyGeneratedScene", path="Assets/Scenes/") # Now proceed with the phased execution plan... # Phase 1: Environment (camera, lights) — no conflicts # Phase 2: Objects (GameObjects) # Phase 3: Materials # etc. ``` ### Wiring Object References Between Components After creating scripts and attaching components, use `set_property` to wire cross-references between GameObjects. Use the `{"name": "ObjectName"}` format to reference scene objects by name: ```python # Wire a list of target GameObjects into a script's serialized field manage_components( action="set_property", target="BeeManager", component_type="BeeManagerScript", property="targetObjects", value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Flower_3"}] ) ``` ### Physics Requirements for Trigger-Based Interactions When scripts use `OnTriggerEnter` / `OnTriggerStay` / `OnTriggerExit`, at least one of the two colliding objects **must** have a `Rigidbody` component. Common pattern: ```python # Moving objects (bees, players) need Rigidbody for triggers to fire batch_execute(commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "Bee_1", "component_type": "Rigidbody" }}, {"tool": "manage_components", "params": { "action": "set_property", "target": "Bee_1", "component_type": "Rigidbody", "properties": {"useGravity": false, "isKinematic": true} }} ]) ``` ### Script Overwrites with `manage_script(action="update")` When a generated script needs to be rewritten (e.g., to add auto-wiring logic), use `update` instead of deleting and recreating: ```python manage_script( action="update", path="Assets/Scripts/MyScript.cs", contents="using UnityEngine;\n\npublic class MyScript : MonoBehaviour { ... }" ) # manage_script update auto-triggers import + compile — just wait and check console # Read mcpforunity://editor/state → wait until is_compiling == false read_console(types=["error"], count=10) ``` --- ## 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_camera(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 (automatically triggers import + compilation) 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. Wait for compilation to finish # Read mcpforunity://editor/state → wait until is_compiling == false # 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. Wait for compilation (script_apply_edits auto-triggers import + compile) # Read mcpforunity://editor/state → wait until is_compiling == false # 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_camera(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']}") ``` ### Instantiate Prefab in Scene Use `manage_gameobject` (not `manage_prefabs`) to place prefab instances in the scene. ```python # Full path manage_gameobject( action="create", name="Enemy_1", prefab_path="Assets/Prefabs/Enemy.prefab", position=[5, 0, 3], parent="Enemies" ) # Smart lookup — just the prefab name works too manage_gameobject(action="create", name="Enemy_2", prefab_path="Enemy", position=[10, 0, 3]) # Batch-spawn multiple instances batch_execute(commands=[ {"tool": "manage_gameobject", "params": { "action": "create", "name": f"Enemy_{i}", "prefab_path": "Enemy", "position": [i * 3, 0, 0], "parent": "Enemies" }} for i in range(5) ]) ``` > **Note:** `manage_prefabs` is for headless prefab editing (inspect, modify contents, create from GameObject). To *instantiate* a prefab into the scene, always use `manage_gameobject(action="create", prefab_path="...")`. --- ## 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. Wait for compilation (create_script auto-triggers import + compile) # Read mcpforunity://editor/state → wait until is_compiling == false # 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_camera(action="screenshot") ``` --- ## UI Creation Workflows Unity has two UI systems: **UI Toolkit** (modern, recommended) and **uGUI** (Canvas-based, legacy). Use `manage_ui` for UI Toolkit workflows, and `batch_execute` with `manage_gameobject` + `manage_components` for uGUI. > **Template warning:** This section is a skill template library, not a guaranteed source of truth. Examples may be inaccurate for your Unity version, package setup, or project conventions. > **Use safely:** > 1. **Always read `mcpforunity://project/info` first** to detect installed packages and input system. > 2. Validate component/property names against the current project. > 3. Prefer targeting by instance ID or full path over generic names. > 4. Treat numeric enum values as placeholders and verify before reuse. ### Step 0: Detect Project UI Capabilities **Before creating any UI**, read project info to determine which packages and input system are available. ```python # Read mcpforunity://project/info — returns: # { # "renderPipeline": "BuiltIn" | "Universal" | "HighDefinition" | "Custom", # "activeInputHandler": "Old" | "New" | "Both", # "packages": { # "ugui": true/false, — com.unity.ugui (Canvas, Image, Button, etc.) # "textmeshpro": true/false, — com.unity.textmeshpro (TextMeshProUGUI) # "inputsystem": true/false, — com.unity.inputsystem (new Input System) # "uiToolkit": true/false, — UI Toolkit (always true for Unity 2021.3+) # "screenCapture": true/false — ScreenCapture module enabled # } # } ``` **Decision matrix:** | project_info field | Value | What to use | |---|---|---| | `packages.uiToolkit` | `true` | **Preferred:** Use `manage_ui` for UI Toolkit (UXML/USS) | | `packages.ugui` | `true` | Canvas-based UI (Image, Button, etc.) via `batch_execute` | | `packages.textmeshpro` | `true` | `TextMeshProUGUI` for text (uGUI) | | `packages.textmeshpro` | `false` | `UnityEngine.UI.Text` (legacy, lower quality) | | `activeInputHandler` | `"Old"` | `StandaloneInputModule` for EventSystem (uGUI) | | `activeInputHandler` | `"New"` | `InputSystemUIInputModule` for EventSystem (uGUI) | | `activeInputHandler` | `"Both"` | Either works; prefer `InputSystemUIInputModule` for UI | ### UI Toolkit Workflows (manage_ui) UI Toolkit uses a web-like approach: **UXML** (like HTML) for structure, **USS** (like CSS) for styling. This is the preferred UI system for new projects. > **Important:** Always use `` (with the `ui:` namespace prefix) in UXML, not bare `