Develop Unity projects from the terminal. Use when creating, editing, building, testing, or debugging Unity games and apps. Covers project setup, C# scripting, scene/prefab/asset YAML editing, editor control, build automation, profiling, and test running — all without leaving pi. Requires Unity 6.4 (6000.4.1f1) installed via Unity Hub.
Control Unity Editor and develop Unity projects entirely from the terminal. Edit scripts, manipulate scenes and prefabs as YAML, compile, test, build, and debug — all through CLI commands that pi can drive directly.
Path resolution: determine $SKILL_DIR from the location of this SKILL.md. Scripts below use that path.
| Component | Path / Command |
|---|---|
| Unity Editor | ~/Unity/Hub/Editor/6000.4.1f1/Editor/Unity |
| Launch editor | unity -projectPath <path> (forces Vulkan) |
| Batch mode | unity-batch <path> [args...] |
| Init project | unity-init-project <path> |
| Editor CLI | unity-ctrl <command> |
| Hub CLI | unity-cli hub -- <args> |
m_SerializationMode: 2 in EditorSettings. Use unity-init-project for new projects; it sets this automatically.unity-ctrl status before sending commands. If Unity is compiling or reloading, wait..unity, .prefab, .asset, or .mat files as text, run unity-ctrl reserialize <path> to validate them through Unity's serializer..cs file, run unity-ctrl editor refresh --compile and then unity-ctrl console --type error to catch errors.exec for inspection — unity-ctrl exec "<C# code>" runs arbitrary C# inside the editor. Use it to query state, count objects, check values.echo '<code>' | unity-ctrl exec to avoid shell escaping issues.unity-ctrl menu "File/Save Project" before any build.unity-init-project ~/Projects/MyGame
This creates a project with:
.meta sidecar).gitignore, .editorconfig, CLAUDE.mdunity -projectPath ~/Projects/MyGame &
unity-ctrl)The editor must have the Connector package for CLI control. Add it via Packages/manifest.json:
cd ~/Projects/MyGame
# Add the connector package
python3 -c "
import json
with open('Packages/manifest.json') as f:
m = json.load(f)
m['dependencies']['com.youngwoocho02.unity-cli-connector'] = 'https://github.com/youngwoocho02/unity-cli.git?path=unity-connector'
with open('Packages/manifest.json', 'w') as f:
json.dump(m, f, indent=2)
"
Then refresh: unity-ctrl editor refresh
For CLI commands to work when Unity is unfocused:
unity-ctrl exec "EditorPrefs.SetInt(\"InteractionMode\", 1); return \"Throttling disabled\";"
Or manually: Edit → Preferences → General → Interaction Mode → No Throttling
unity-ctrl status
Expected: Unity (port 8090): ready
Run the pre-flight check:
bash "$SKILL_DIR/scripts/preflight.sh" ~/Projects/MyGame
This verifies: editor connection, Force Text serialization, Connector package, and reports any issues.
Scripts live in Assets/Scripts/ (or any subfolder under Assets/). Unity hot-reloads when files change.
# Edit a script
edit Assets/Scripts/PlayerController.cs
# Trigger recompilation and wait
unity-ctrl editor refresh --compile
# Check for errors
unity-ctrl console --type error
# If clean:
unity-ctrl console --type error --lines 0
# Shows nothing = no errors
Convention: one MonoBehaviour per file, filename matches class name, use namespaces matching folder structure.
Use exec to run C# inside Unity. Always use return to get output.
# Get active scene name
unity-ctrl exec "return EditorSceneManager.GetActiveScene().name;"
# Count GameObjects in scene
unity-ctrl exec "return GameObject.FindObjectsOfType<GameObject>().Length;"
# List all scripts on a named object
unity-ctrl exec "var go = GameObject.Find(\"Player\"); return string.Join(\", \", go.GetComponents<Component>().Select(c => c.GetType().Name));" --usings System.Linq
# Check a serialized field value
unity-ctrl exec "var p = GameObject.Find(\"Player\").GetComponent<PlayerController>(); return p.moveSpeed;"
# Get project path
unity-ctrl exec "return Application.dataPath;"
For complex queries, pipe via stdin:
echo '
var objects = GameObject.FindObjectsOfType<Transform>();
var result = "";
foreach (var t in objects) {
result += t.gameObject.name + " pos=" + t.position + "\n";
}
return result;
' | unity-ctrl exec
Unity scenes (.unity) and prefabs (.prefab) are YAML when Force Text is enabled. See references/yaml-editing.md for the full format guide.
Read and understand structure:
# View a scene file
read Assets/Scenes/SampleScene.unity
# Find a specific GameObject by name
grep -n "m_Name:" Assets/Scenes/SampleScene.unity
# Find all Transform components
grep -n "Transform:" Assets/Scenes/SampleScene.unity
Modify and validate:
# Edit the file (e.g., change a transform position)
edit Assets/Scenes/SampleScene.unity
# CRITICAL: reserialize to validate through Unity's serializer
unity-ctrl reserialize Assets/Scenes/SampleScene.unity
# Refresh to pick up changes
unity-ctrl editor refresh
Prefer exec for complex scene modifications — it's safer than editing YAML directly:
# Add a cube to the scene
unity-ctrl exec "var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.position = new Vector3(0, 1, 0); cube.name = \"MyCube\"; return cube.name;"
# Save the scene
unity-ctrl menu "File/Save"
# Enter play mode
unity-ctrl editor play --wait
# Check if playing
unity-ctrl exec "return Application.isPlaying;"
# Pause
unity-ctrl editor pause
# Stop
unity-ctrl editor stop
# Run EditMode tests
unity-ctrl test
# Run PlayMode tests
unity-ctrl test --mode PlayMode
# Filter by test name
unity-ctrl test --filter PlayerMovementTests
# Run tests in batch mode (no GUI needed)
unity-batch ~/Projects/MyGame -runTests -testPlatform EditMode -quit
# Save project first
unity-ctrl menu "File/Save Project"
# Build via batch mode with a custom build method
unity-batch ~/Projects/MyGame \
-executeMethod BuildScript.PerformBuild \
-quit
# Or build Linux standalone directly
unity-batch ~/Projects/MyGame \
-buildLinux64Player ~/Projects/MyGame/Build/game \
-quit
Create Assets/Editor/BuildScript.cs for custom builds:
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class BuildScript
{
public static void PerformBuild()
{
var options = new BuildPlayerOptions
{
scenes = new[] { "Assets/Scenes/SampleScene.unity" },
locationPathName = "Build/game",
target = BuildTarget.StandaloneLinux64,
options = BuildOptions.None
};
BuildPipeline.BuildPlayer(options);
}
}
# Read console errors and warnings
unity-ctrl console --type error,warning
# Read all logs with stack traces
unity-ctrl console --type error,warning,log --stacktrace full --lines 50
# Clear console
unity-ctrl console --clear
# Take a screenshot of the scene view
unity-ctrl screenshot --output_path /tmp/scene.png
# Take a screenshot of the game view
unity-ctrl screenshot --view game --output_path /tmp/game.png
# Profile the last frame
unity-ctrl profiler hierarchy --depth 3
# Profile a specific system
unity-ctrl profiler hierarchy --root "PlayerLoop" --depth 4 --min 0.5
# Average over 30 frames
unity-ctrl profiler hierarchy --frames 30 --sort self --min 0.1
# Add a package via manifest.json
cd ~/Projects/MyGame
cat Packages/manifest.json # inspect current packages
# Add a package entry (e.g., Cinemachine)
python3 -c "
import json
with open('Packages/manifest.json') as f:
m = json.load(f)
m['dependencies']['com.unity.cinemachine'] = '3.1.3'
with open('Packages/manifest.json', 'w') as f:
json.dump(m, f, indent=2)
"
# Refresh to trigger package resolution
unity-ctrl editor refresh
For project-specific operations, create [UnityCliTool] classes in an Editor assembly. See references/custom-tools.md.
MyGame/
├── Assets/
│ ├── Scenes/ # .unity (YAML)
│ ├── Scripts/ # .cs (C# source)
│ ├── Prefabs/ # .prefab (YAML)
│ ├── Materials/ # .mat (YAML)
│ ├── Editor/ # Editor-only scripts (BuildScript, custom inspectors)
│ └── Resources/ # Runtime-loadable assets
├── Packages/
│ └── manifest.json # Package dependencies
├── ProjectSettings/ # All settings files (YAML, version-controlled)
│ ├── EditorSettings.asset
│ ├── ProjectSettings.asset
│ ├── TagManager.asset
│ └── ...
├── .gitignore
├── .editorconfig
└── CLAUDE.md # AI agent instructions
| Problem | Fix |
|---|---|
unity-ctrl status → connection refused | Unity isn't open, or Connector package not installed |
| Commands hang / no response | Editor throttling is on; set Interaction Mode to No Throttling |
| Compile errors after script edit | unity-ctrl console --type error to see what's wrong |
| Scene flickering / artifacts | Ensure Vulkan is active, not OpenGLCore (unity -force-vulkan) |
| YAML edit broke a prefab | Run unity-ctrl reserialize <path> to fix it |
| Unity Hub won't launch | Use unityhub-fix (applies GTK3 workaround for Ubuntu 22.04) |
exec shell escaping issues | Pipe via stdin: echo '<code>' | unity-ctrl exec |
| Multiple Unity instances | Use unity-ctrl --project <path> or --port <N> to target one |
Load these on-demand when you need deeper detail:
unity-ctrl command reference with all flags[UnityCliTool] commands