【Unity】Unity エディタ拡張入門:開発効率を劇的に向上させるツールの作り方

作成: 2026-02-05

Unity Editorの機能を拡張し、開発効率を向上させる技術を解説。MenuItem、EditorWindow、CustomEditor、PropertyDrawerなど、様々なエディタ拡張の作り方を実践的なコード例とともに紹介します。

概要

動作確認環境: Unity 2022.3 LTS / Unity 6

「繰り返し作業が多くて時間がかかる…」「チーム内で作業を標準化したい…」「デザイナーやプランナーが使いやすいツールを作りたい…」

Unity開発では、このような課題に直面することがあります。エディタ拡張 は、Unity Editorの機能を拡張し、開発効率を向上させる技術です。カスタムウィンドウの作成、インスペクターのカスタマイズ、メニュー項目の追加など、プロジェクト固有のワークフローに対応したツールを作成できます。

Editorフォルダの配置

エディタ拡張のスクリプトは、必ず Editorフォルダ 内に配置する必要があります。

なぜEditorフォルダが必要なのか

  • Unity Editorでのみ使用するコードを分離
  • ビルド時に自動的に除外される
  • エディタ専用のAPIを使用できる
  • コンパイル順序の制御

配置場所

  • プロジェクト内のどこにでも配置可能
  • 一般的にはAssets/Editorに配置
  • 複数のEditorフォルダを持つことも可能
  • エディタ拡張で使用するリソース(画像、フォントなど)はEditor Default Resourcesフォルダに配置

MenuItemは、Unity Editorのメニューバーに項目を追加する最もシンプルなエディタ拡張です。

基本的な使い方

using UnityEngine;
using UnityEditor;

public class MenuItemExample
{
    [MenuItem("Tools/Do Something")]
    static void DoSomething()
    {
        Debug.Log("Something!");
    }
}

この例では、Tools > Do Somethingというメニュー項目が追加されます。

ショートカットキーの設定

[MenuItem("Tools/Do Something %g")] // Ctrl+G (Windows) / Cmd+G (Mac)
static void DoSomething()
{
    Debug.Log("Something!");
}

ショートカットキーの記号:

  • %:Ctrl (Windows) / Cmd (Mac)
  • #:Shift
  • &:Alt
  • _:キー(例:_gはG)

メニュー項目の有効/無効

[MenuItem("Tools/Do Something")]
static void DoSomething()
{
    Debug.Log("Something!");
}

[MenuItem("Tools/Do Something", true)]
static bool ValidateDoSomething()
{
    // 何かが選択されている場合のみ有効
    return Selection.activeGameObject != null;
}

コンテキストメニューの追加

[MenuItem("CONTEXT/Transform/Reset Position")]
static void ResetPosition(MenuCommand command)
{
    Transform transform = (Transform)command.context;
    Undo.RecordObject(transform, "Reset Position");
    transform.position = Vector3.zero;
}

重要: オブジェクトを変更する際はUndo.RecordObject()でUndo操作に対応してください。Undo.RecordObject()を使用する場合、EditorUtility.SetDirty()不要 です(Undo自体が変更を追跡するため)。

EditorWindow:カスタムウィンドウの作成

EditorWindowは、独自のウィンドウを作成できる強力な機能です。

基本的な使い方

using UnityEngine;
using UnityEditor;

public class MyEditorWindow : EditorWindow
{
    [MenuItem("Window/My Editor Window")]
    static void Open()
    {
        GetWindow<MyEditorWindow>("My Editor Window");
    }

    void OnGUI()
    {
        GUILayout.Label("Hello, Editor Window!");
    }
}

UI Toolkitを使用する場合(Unity 2021以降)

Unity 2022以降では、UI Toolkit がエディタ拡張の推奨UIシステムになっています。

using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;

public class MyEditorWindow : EditorWindow
{
    [MenuItem("Window/My Editor Window")]
    static void Open()
    {
        GetWindow<MyEditorWindow>("My Editor Window");
    }

    void CreateGUI()
    {
        // ラベル
        var label = new Label("Hello, UI Toolkit!");
        rootVisualElement.Add(label);

        // テキストフィールド
        var textField = new TextField("Name");
        textField.RegisterValueChangedCallback(evt => Debug.Log(evt.newValue));
        rootVisualElement.Add(textField);

        // ボタン
        var button = new Button(() => Debug.Log("Clicked!")) { text = "Click Me" };
        rootVisualElement.Add(button);
    }
}

UI Toolkitの利点: スタイルシート(USS)でデザインを分離でき、データバインディングにも対応しています。新規エディタ拡張ではUI Toolkitの使用を検討してください。

データの保存

public class MyEditorWindow : EditorWindow
{
    string text = "";

    void OnGUI()
    {
        text = EditorGUILayout.TextField("Text", text);
    }

    void OnEnable()
    {
        text = EditorPrefs.GetString("MyEditorWindow_Text", "");
    }

    void OnDisable()
    {
        EditorPrefs.SetString("MyEditorWindow_Text", text);
    }
}

CustomEditor:インスペクターのカスタマイズ

CustomEditorは、特定のコンポーネントのインスペクター表示をカスタマイズできます。

基本的な使い方(推奨:SerializedObject)

// コンポーネント
using UnityEngine;

public class MyComponent : MonoBehaviour
{
    public int value;
    public string text;
}
// カスタムエディタ(推奨パターン)
using UnityEditor;

[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
    SerializedProperty valueProp;
    SerializedProperty textProp;

    void OnEnable()
    {
        valueProp = serializedObject.FindProperty("value");
        textProp = serializedObject.FindProperty("text");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        EditorGUILayout.PropertyField(valueProp);
        EditorGUILayout.PropertyField(textProp);

        if (GUILayout.Button("Reset"))
        {
            valueProp.intValue = 0;
            textProp.stringValue = "";
        }

        serializedObject.ApplyModifiedProperties();
    }
}

重要: SerializedObjectを使用することで、Undo/Redo対応複数オブジェクト同時編集Prefabオーバーライド検出 が自動的に有効になります。EditorUtility.SetDirty()を呼ぶ必要はありません。

⚠️ targetを直接操作する方法(非推奨)

警告: この方法はUndo/Redo非対応・マルチオブジェクト編集非対応のため、上記のSerializedObject版を推奨します。既存コードの理解のためにのみ記載します。

// 非推奨パターン - Undo/Redo非対応
[CustomEditor(typeof(MyComponent))]
public class MyComponentEditor : Editor
{
    public override void OnInspectorGUI()
    {
        MyComponent myComponent = (MyComponent)target;

        // 直接編集はUndo非対応、マルチ編集非対応
        myComponent.value = EditorGUILayout.IntField("Value", myComponent.value);
        myComponent.text = EditorGUILayout.TextField("Text", myComponent.text);

        if (GUI.changed)
        {
            EditorUtility.SetDirty(target);  // 変更をUnityに通知(非推奨パターン)
        }
    }
}

SetDirtyについて: SerializedObject.ApplyModifiedProperties()を使用する場合、EditorUtility.SetDirty()不要 です。SetDirty()targetを直接操作する場合のみ必要ですが、そのパターン自体が非推奨です。

SerializedObjectのメリット

  • Undo/Redo: 自動的に対応
  • マルチ編集: 複数オブジェクト同時選択時も動作
  • Prefab: オーバーライドの太字表示が自動
  • SetDirty不要: ApplyModifiedProperties()が自動的に変更を通知

EditorGUILayoutリファレンス

よく使うEditorGUILayoutのフィールドをまとめた参照表です。

メソッド用途戻り値
IntField整数入力int
FloatField小数入力float
TextField文字列入力string
Toggleチェックボックスbool
Popupドロップダウン選択int(インデックス)
EnumPopupEnum選択Enum
ObjectFieldオブジェクト参照Object
Vector3FieldVector3入力Vector3
ColorField色選択Color
Sliderスライダーfloat
IntSlider整数スライダーint
Foldout折りたたみbool
BeginHorizontal/EndHorizontal横並びレイアウト-
BeginVertical/EndVertical縦並びレイアウト-
Spaceスペース追加-

OnSceneGUI:シーンビューへの描画

CustomEditorでOnSceneGUIを実装すると、シーンビューにハンドルやガイドを描画できます。

// ウェイポイント管理コンポーネント
using UnityEngine;

public class WaypointManager : MonoBehaviour
{
    public Transform[] waypoints;
}
// カスタムエディタ(OnSceneGUI実装)
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(WaypointManager))]
public class WaypointManagerEditor : Editor
{
    void OnSceneGUI()
    {
        WaypointManager manager = (WaypointManager)target;

        // ウェイポイント間に線を描画
        Handles.color = Color.yellow;
        for (int i = 0; i < manager.waypoints.Length - 1; i++)
        {
            Handles.DrawLine(
                manager.waypoints[i].position,
                manager.waypoints[i + 1].position
            );
        }

        // 移動可能なハンドルを表示
        for (int i = 0; i < manager.waypoints.Length; i++)
        {
            EditorGUI.BeginChangeCheck();
            Vector3 newPos = Handles.PositionHandle(
                manager.waypoints[i].position,
                Quaternion.identity
            );
            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(manager.waypoints[i], "Move Waypoint");
                manager.waypoints[i].position = newPos;
            }
        }
    }
}

活用例: ウェイポイントの配置、攻撃範囲の調整、スポーン位置の設定など、シーンビュー上で直感的に編集したい場合に便利です。

PropertyDrawer:プロパティの表示カスタマイズ

PropertyDrawerは、特定のプロパティの表示をカスタマイズできます。

基本的な使い方

// カスタムクラス
using UnityEngine;

[System.Serializable]
public class Range
{
    public float min;
    public float max;
}
// PropertyDrawer
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(Range))]
public class RangeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        var minRect = new Rect(position.x, position.y, position.width / 2 - 5, position.height);
        var maxRect = new Rect(position.x + position.width / 2 + 5, position.y, position.width / 2 - 5, position.height);

        EditorGUI.PropertyField(minRect, property.FindPropertyRelative("min"), GUIContent.none);
        EditorGUI.PropertyField(maxRect, property.FindPropertyRelative("max"), GUIContent.none);

        EditorGUI.EndProperty();
    }
}

PropertyAttributeの使用(ReadOnly属性の例)

// 属性の定義
using UnityEngine;

public class ReadOnlyAttribute : PropertyAttribute
{
}
// PropertyDrawer
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label, true);
        GUI.enabled = true;
    }
}

使用例:

public class MyComponent : MonoBehaviour
{
    [ReadOnly]
    public int readOnlyValue;
}

AssetDatabase:アセットの操作

AssetDatabaseは、プロジェクト内のアセットを操作するためのAPIです。

// すべてのPrefabを検索
string[] guids = AssetDatabase.FindAssets("t:Prefab");
foreach (string guid in guids)
{
    string path = AssetDatabase.GUIDToAssetPath(guid);
    GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
    Debug.Log(prefab.name);
}

// ScriptableObjectの作成
MyScriptableObject asset = ScriptableObject.CreateInstance<MyScriptableObject>();
AssetDatabase.CreateAsset(asset, "Assets/MyAsset.asset");
AssetDatabase.SaveAssets();

// アセットの削除・リネーム・移動
AssetDatabase.DeleteAsset("Assets/MyAsset.asset");
AssetDatabase.RenameAsset("Assets/OldName.asset", "NewName");
AssetDatabase.MoveAsset("Assets/OldPath/MyAsset.asset", "Assets/NewPath/MyAsset.asset");

Selection:選択中のオブジェクト

Selectionクラスは、Unity Editorで選択中のオブジェクトを取得・設定できます。

// 選択中のオブジェクトを取得
GameObject selectedObject = Selection.activeGameObject;

// 複数選択の取得
GameObject[] selectedObjects = Selection.gameObjects;
foreach (GameObject obj in selectedObjects)
{
    Debug.Log(obj.name);
}

// オブジェクトを選択
GameObject obj = GameObject.Find("MyObject");
Selection.activeGameObject = obj;

Undo:Undo/Redoの実装

Undoクラスは、エディタ拡張でUndo/Redo機能を実装できます。

// オブジェクトの変更を記録
Undo.RecordObject(myObject, "Change Value");
myObject.value = 10;

// オブジェクトの作成を記録
GameObject obj = new GameObject("New Object");
Undo.RegisterCreatedObjectUndo(obj, "Create Object");

// オブジェクトの削除を記録
Undo.DestroyObjectImmediate(obj);

実践的な使用例

例1:選択中のオブジェクトをすべて削除

[MenuItem("Tools/Delete Selected Objects")]
static void DeleteSelectedObjects()
{
    if (Selection.gameObjects.Length == 0)
    {
        Debug.LogWarning("No objects selected.");
        return;
    }

    foreach (GameObject obj in Selection.gameObjects)
    {
        Undo.DestroyObjectImmediate(obj);
    }
}

[MenuItem("Tools/Delete Selected Objects", true)]
static bool ValidateDeleteSelectedObjects()
{
    return Selection.gameObjects.Length > 0;
}

例2:カスタムウィンドウでアセットを一覧表示

public class AssetBrowserWindow : EditorWindow
{
    List<GameObject> prefabs = new List<GameObject>();
    Vector2 scrollPosition;

    [MenuItem("Window/Asset Browser")]
    static void Open()
    {
        GetWindow<AssetBrowserWindow>("Asset Browser");
    }

    void OnEnable()
    {
        LoadPrefabs();
    }

    void LoadPrefabs()
    {
        prefabs.Clear();
        string[] guids = AssetDatabase.FindAssets("t:Prefab");
        foreach (string guid in guids)
        {
            string path = AssetDatabase.GUIDToAssetPath(guid);
            GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(path);
            prefabs.Add(prefab);
        }
    }

    void OnGUI()
    {
        if (GUILayout.Button("Reload"))
        {
            LoadPrefabs();
        }

        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

        foreach (GameObject prefab in prefabs)
        {
            EditorGUILayout.ObjectField(prefab, typeof(GameObject), false);
        }

        EditorGUILayout.EndScrollView();
    }
}

ベストプラクティス

  • Editorフォルダに配置 - エディタ拡張のスクリプトは必ずEditorフォルダ内に
  • Undo/Redoの実装 - オブジェクトを変更する場合は必ずUndo.RecordObjectを使用
  • SerializedObjectの使用 - CustomEditorではSerializedObjectを使用して複数オブジェクト編集に対応
  • エラーハンドリング - try-catchでエラーを適切に処理し、エディタの安定性を保つ
  • パフォーマンスの考慮 - OnGUIは毎フレーム呼ばれるため、重い処理はキャッシュを活用

まとめ

Unity エディタ拡張は、開発効率を大幅に向上させる強力な技術です。

  • MenuItem - メニューバーに項目を追加、ショートカットキーも設定可能
  • EditorWindow - カスタムウィンドウを作成、複雑なツールを構築
  • CustomEditor - インスペクターをカスタマイズ、使いやすいUIを提供
  • PropertyDrawer - 特定のプロパティの表示をカスタマイズ

繰り返し作業を自動化し、チーム内での作業を標準化し、プロジェクト固有のワークフローに対応したツールを作成しましょう。

さらに学ぶために