From unity-master
Guides Unity editor scripting for custom inspectors, EditorWindows, PropertyDrawers; build pipelines, CI/CD with GameCI/GitHub Actions; Unity Test Framework; version control and packages.
npx claudepluginhub josiahsiegel/claude-plugin-marketplace --plugin unity-masterThis skill uses the workspace's default tool permissions.
Reference for extending the Unity Editor, automating builds, testing, version control configuration, and package development. Covers custom inspectors, editor windows, build pipeline scripting, CI/CD, and the Unity Test Framework.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Reference for extending the Unity Editor, automating builds, testing, version control configuration, and package development. Covers custom inspectors, editor windows, build pipeline scripting, CI/CD, and the Unity Test Framework.
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(EnemySpawner))]
public class EnemySpawnerEditor : Editor
{
SerializedProperty spawnPoints;
SerializedProperty enemyPrefab;
SerializedProperty spawnInterval;
void OnEnable()
{
spawnPoints = serializedObject.FindProperty("_spawnPoints");
enemyPrefab = serializedObject.FindProperty("_enemyPrefab");
spawnInterval = serializedObject.FindProperty("_spawnInterval");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(enemyPrefab);
EditorGUILayout.Slider(spawnInterval, 0.1f, 10f, new GUIContent("Spawn Interval"));
EditorGUILayout.Space();
EditorGUILayout.LabelField("Spawn Points", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(spawnPoints, true);
if (GUILayout.Button("Add Spawn Point at Origin"))
{
spawnPoints.InsertArrayElementAtIndex(spawnPoints.arraySize);
var newElement = spawnPoints.GetArrayElementAtIndex(spawnPoints.arraySize - 1);
newElement.vector3Value = Vector3.zero;
}
serializedObject.ApplyModifiedProperties();
}
void OnSceneGUI()
{
var spawner = (EnemySpawner)target;
// Draw handles in scene view for each spawn point
for (int i = 0; i < spawner.SpawnPointCount; i++)
{
Vector3 point = spawner.GetSpawnPoint(i);
Vector3 newPoint = Handles.PositionHandle(point, Quaternion.identity);
if (point != newPoint)
{
Undo.RecordObject(spawner, "Move Spawn Point");
spawner.SetSpawnPoint(i, newPoint);
}
}
}
}
#endif
Key rules:
#if UNITY_EDITOR or place in Editor/ foldersSerializedProperty for undo/redo support and multi-object editingserializedObject.Update() before and ApplyModifiedProperties() after changesUndo.RecordObject() before direct modifications[CustomPropertyDrawer(typeof(MinMaxRange))]
public class MinMaxRangeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var min = property.FindPropertyRelative("min");
var max = property.FindPropertyRelative("max");
float minVal = min.floatValue;
float maxVal = max.floatValue;
position = EditorGUI.PrefixLabel(position, label);
EditorGUI.MinMaxSlider(position, ref minVal, ref maxVal, 0f, 100f);
min.floatValue = minVal;
max.floatValue = maxVal;
EditorGUI.EndProperty();
}
}
Use PropertyDrawers for reusable field-level customization. Use CustomEditors for component-level customization.
public class LevelEditorWindow : EditorWindow
{
[MenuItem("Tools/Level Editor")]
static void ShowWindow() => GetWindow<LevelEditorWindow>("Level Editor");
Vector2 scrollPos;
string searchFilter = "";
void OnGUI()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
searchFilter = EditorGUILayout.TextField(searchFilter, EditorStyles.toolbarSearchField);
if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60)))
RefreshData();
EditorGUILayout.EndHorizontal();
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
// Draw content
EditorGUILayout.EndScrollView();
}
void OnSelectionChange() => Repaint(); // React to selection changes
}
Use EditorWindow for standalone tools. Use [MenuItem] for menu bar integration. Override OnSelectionChange, OnHierarchyChange, OnProjectChange for reactive updates.
[ScriptedImporter(1, "leveldata")]
public class LevelDataImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
string json = File.ReadAllText(ctx.assetPath);
var levelData = ScriptableObject.CreateInstance<LevelData>();
JsonUtility.FromJsonOverwrite(json, levelData);
ctx.AddObjectToAsset("main", levelData);
ctx.SetMainObject(levelData);
}
}
Register custom file extensions. Unity re-imports automatically when the source file changes.
public static class BuildScript
{
[MenuItem("Build/Build Windows")]
public static void BuildWindows()
{
var options = new BuildPlayerOptions
{
scenes = EditorBuildSettings.scenes
.Where(s => s.enabled)
.Select(s => s.path).ToArray(),
locationPathName = "Builds/Windows/Game.exe",
target = BuildTarget.StandaloneWindows64,
options = BuildOptions.None
};
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result != BuildResult.Succeeded)
throw new Exception($"Build failed: {report.summary.totalErrors} errors");
}
}
name: Unity Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
targetPlatform: [StandaloneWindows64, StandaloneLinux64, WebGL]
steps:
- uses: actions/checkout@v4
with:
lfs: true
- uses: game-ci/unity-builder@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
with:
targetPlatform: ${{ matrix.targetPlatform }}
buildMethod: BuildScript.Build${{ matrix.targetPlatform }}
- uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.targetPlatform }}
path: build/${{ matrix.targetPlatform }}
Activate a Unity license first using game-ci/unity-activate. Use Unity Build Automation (Cloud Build) as an alternative if CI runners lack capacity.
Project/
├── Scripts/
│ ├── Core/ Core.asmdef (no references)
│ ├── Gameplay/ Gameplay.asmdef (references: Core)
│ ├── UI/ UI.asmdef (references: Core, Gameplay)
│ ├── Networking/ Networking.asmdef (references: Core)
│ ├── Editor/ Editor.asmdef (references: Core; Editor-only platform)
│ └── Tests/
│ ├── EditMode/ Tests.EditMode.asmdef (references: Core; Test assemblies)
│ └── PlayMode/ Tests.PlayMode.asmdef (references: Core, Gameplay)
Benefits: Incremental compilation (only recompile changed assemblies), enforced dependency boundaries, required for testability.
Rules:
UnityEngine.TestRunner and UnityEditor.TestRunner[TestFixture]
public class InventoryTests
{
[Test]
public void AddItem_IncreasesCount()
{
var inventory = new Inventory(maxSlots: 10);
inventory.Add(new Item("Sword", 1));
Assert.AreEqual(1, inventory.Count);
}
[Test]
public void AddItem_WhenFull_ReturnsFalse()
{
var inventory = new Inventory(maxSlots: 1);
inventory.Add(new Item("Sword", 1));
Assert.IsFalse(inventory.Add(new Item("Shield", 1)));
}
}
[UnityTest]
public IEnumerator Player_TakesDamage_HealthDecreases()
{
var player = new GameObject().AddComponent<PlayerHealth>();
player.Initialize(100);
player.TakeDamage(30);
yield return null; // Wait one frame
Assert.AreEqual(70, player.CurrentHealth);
}
Run tests via Window > General > Test Runner. Edit Mode tests run instantly. Play Mode tests enter play mode and can test MonoBehaviour logic, coroutines, and physics.
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
/[Mm]emoryCaptures/
/[Rr]ecordings/
*.csproj
*.sln
*.suo
*.user
*.pidb
*.booproj
*.unityproj
.gitattributes with *.unity merge=unityyamlmergecom.company.my-package/
├── package.json
├── Runtime/
│ ├── com.company.my-package.asmdef
│ └── MyScript.cs
├── Editor/
│ ├── com.company.my-package.editor.asmdef
│ └── MyEditorScript.cs
├── Tests/
│ ├── Runtime/
│ └── Editor/
├── Documentation~/
├── CHANGELOG.md
└── README.md
Install local packages via manifest.json: "com.company.my-package": "file:../../Packages/my-package" or via git URL.
references/editor-recipes.md -- Advanced editor patterns: Gizmos, Handles, SceneView overlays, custom menus, asset post-processors, build hooks (IPreprocessBuildWithReport), serialization callbacks, Terrain and ProBuilder scripting, multi-scene editing workflows