Unity Editorのメインツールバー拡張APIとは?

Unity 6.3から、Unity Editor上部のメインツールバー(Playボタンやクラウドアイコンがあるバー)に、独自のUI要素を追加できる公式APIが導入されました。

これまでメインツールバーへの要素追加は公式にサポートされておらず、非公式なハックに頼るしかありませんでした。

Unity 6.3で追加されたMainToolbarElement系のAPIを使うと、エディタ起動中に常に表示されるメインツールバーへ、自作のボタンやドロップダウン、トグルを配置できます。

シーン切り替え、設定パネル、デバッグモード切替など、日常的に触る操作をメインツールバーにまとめておくと、メニューを辿らずワンクリックで実行可能。メインウィンドウ上部に常駐する都合上、SceneViewのオーバーレイより視認性も高くなります。

使用できる環境

必要なUnityバージョン

  • Unity 6.3以降が必須
  • Unity 6.2以前のバージョンでは利用できません

名前空間

MainToolbarElement系のAPIはUnityEditor.Toolbars名前空間に定義されています。

使用するスクリプトはエディタ専用(Editorフォルダ内もしくはasmdefでEditorプラットフォーム指定)に配置してください。

メインツールバーAPIの基本構造

MainToolbarElementAttributeの役割

メインツールバーに要素を追加するには、MainToolbarElementAttribute属性をstaticメソッドに付与します。

[MainToolbarElement("Examples/MyButton",
    defaultDockPosition = MainToolbarDockPosition.Middle)]
public static MainToolbarElement CreateMyButton()
{
    // MainToolbarElementを返す
}

属性を付与したstaticメソッドは、以下の戻り値のいずれかを持ちます。

  • MainToolbarElement … 1つの要素を返す
  • IEnumerable<MainToolbarElement> … 複数の要素をまとめて返す

要素の種類

MainToolbarElementを継承した具象クラスがいくつか用意されています。

クラス用途
MainToolbarButtonクリックで処理を実行するボタン
MainToolbarDropdownクリックでGenericMenuなどのドロップダウンを表示
MainToolbarToggleON/OFFを切り替えるトグルボタン
MainToolbarSlider数値を調整するスライダー
MainToolbarLabel情報表示用のラベル

配置位置の指定

属性のdefaultDockPosition引数で、初期配置位置を指定します。

public enum MainToolbarDockPosition
{
    Left,    // ツールバー左側
    Middle,  // ツールバー中央
    Right,   // ツールバー右側
}

同じ位置内での並び順はdefaultDockIndexで指定できます。

これらは初期値であり、ユーザーがメインツールバー上で右クリックしてCustomizeメニューから並び替え可能です。

MainToolbarContent

各要素のテキスト・アイコン・ツールチップはMainToolbarContentにまとめて指定します。

// テキストのみ
var content = new MainToolbarContent("Hello");

// アイコンのみ
var icon = EditorGUIUtility.IconContent("SettingsIcon").image as Texture2D;
var content = new MainToolbarContent(icon);

// テキスト+アイコン+ツールチップ
var content = new MainToolbarContent("Hello", icon, "ツールチップの説明");

基本的な実装例

シンプルなボタンの追加

最も基本的な例として、クリックでログを出力するボタンをメインツールバーに追加します。

using UnityEditor;
using UnityEditor.Toolbars;
using UnityEngine;

public static class SimpleToolbarButton
{
    // 属性をstaticメソッドに付与
    [MainToolbarElement("Examples/HelloButton",
        defaultDockPosition = MainToolbarDockPosition.Middle)]
    public static MainToolbarElement CreateButton()
    {
        // アイコンの取得(Unityビルトインアイコン)
        var icon = EditorGUIUtility.IconContent("console.infoicon").image as Texture2D;

        // 表示内容を組み立てる
        var content = new MainToolbarContent("Hello", icon, "ログを出力します");

        // ボタンを返す(第2引数がクリック時のコールバック)
        return new MainToolbarButton(content, () =>
        {
            Debug.Log("メインツールバーのボタンがクリックされました!");
        });
    }
}

UnityEditorのツールバーの辺りを右クリックすると、作成した要素を追加するためのメニューが表示されます。

押さえておきたい点。

  • 属性を付けるのはstaticメソッド。インスタンスメソッドやクラスに付けても認識されない
  • 第1引数の"Examples/HelloButton"は要素を一意に識別するパス。Customizeメニューにも同じ名前で出てくる
  • このメソッドはMainToolbar.Refreshで何度も呼ばれる可能性があるので、副作用のない実装にしておく

アイコン画像の選び方

EditorGUIUtility.IconContentでUnity組み込みのエディタアイコンを取得できます。

"SettingsIcon""d_Refresh""PlayButton"などプロジェクト内外でよく使われるアイコン名を指定します。

独自のアイコンを使う場合は、EditorGUIUtility.LoadAssetDatabase.LoadAssetAtPathTexture2Dとして読み込み、MainToolbarContentに渡します。

実用的な実装例

シーン切り替えドロップダウン

MainToolbarDropdownGenericMenuを組み合わせて、ビルド設定に登録されたシーンを一覧表示するドロップダウンを作ります。

using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.Toolbars;
using UnityEngine;

public static class SceneSelectorToolbar
{
    private const string ElementPath = "SceneSelector/Dropdown";

    [MainToolbarElement(ElementPath,
        defaultDockPosition = MainToolbarDockPosition.Left)]
    public static MainToolbarElement CreateDropdown()
    {
        var content = new MainToolbarContent(
            "Scenes",
            EditorGUIUtility.IconContent("SceneAsset Icon").image as Texture2D,
            "ビルド設定のシーンを開く");

        // クリック時にドロップダウンを表示
        return new MainToolbarDropdown(content, ShowSceneMenu);
    }

    private static void ShowSceneMenu(Rect dropDownRect)
    {
        var menu = new GenericMenu();
        var scenes = EditorBuildSettings.scenes;

        if (scenes.Length == 0)
        {
            menu.AddDisabledItem(new GUIContent("シーンが登録されていません"));
        }
        else
        {
            foreach (var scene in scenes)
            {
                if (!scene.enabled) continue;

                var scenePath = scene.path;
                var sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);

                menu.AddItem(new GUIContent(sceneName), false, () =>
                {
                    // 未保存の変更があれば保存ダイアログを出す
                    if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
                    {
                        EditorSceneManager.OpenScene(scenePath);
                    }
                });
            }
        }

        // 引数のRectは要素の表示位置。そこを基準にメニューを展開
        menu.DropDown(dropDownRect);
    }
}

MainToolbarDropdownのコールバックはRectを引数で受け取ります。これをそのままGenericMenu.DropDown()に渡せば、要素の真下にメニューが開く、という仕組み。位置調整のコードを書かなくて済むので楽です。

デバッグモードの切り替え

MainToolbarToggleを使って、EditorPrefsに状態を保存するデバッグモード切替を実装します。

using UnityEditor;
using UnityEditor.Toolbars;
using UnityEngine;

public static class DebugModeToolbar
{
    private const string PrefsKey = "CustomToolbar_DebugMode";

    [MainToolbarElement("Debug/ModeToggle",
        defaultDockPosition = MainToolbarDockPosition.Right)]
    public static MainToolbarElement CreateToggle()
    {
        // 保存された初期状態を読み込む
        var initialValue = EditorPrefs.GetBool(PrefsKey, false);

        var icon = EditorGUIUtility.IconContent("DebuggerDisabled").image as Texture2D;
        var content = new MainToolbarContent("Debug", icon, "デバッグモードの切り替え");

        // 第2引数が初期値、第3引数が値変更時のコールバック
        return new MainToolbarToggle(content, initialValue, OnValueChanged);
    }

    private static void OnValueChanged(bool newValue)
    {
        // 状態を保存
        EditorPrefs.SetBool(PrefsKey, newValue);

        // デバッグモードを適用
        ApplyDebugMode(newValue);
    }

    private static void ApplyDebugMode(bool enabled)
    {
        Debug.Log($"デバッグモード: {(enabled ? "ON" : "OFF")}");
        // 例: Debug.unityLogger.logEnabled = enabled;
    }
}

MainToolbarToggleのコンストラクタは(content, initialValue, Action<bool>)という構成で、EditorToolbarToggleのようにインスタンスのイベント購読をする形式ではない点に注意してください。

ラベルによる情報表示

MainToolbarLabelは、現在のブランチ名やビルドターゲットなどの情報をメインツールバーに常時表示したいときに便利です。

using UnityEditor;
using UnityEditor.Toolbars;
using UnityEngine;

public static class BuildTargetLabelToolbar
{
    [MainToolbarElement("Info/BuildTargetLabel",
        defaultDockPosition = MainToolbarDockPosition.Right)]
    public static MainToolbarElement CreateLabel()
    {
        var target = EditorUserBuildSettings.activeBuildTarget.ToString();
        var icon = EditorGUIUtility.IconContent("BuildSettings.Standalone").image as Texture2D;

        var content = new MainToolbarContent(
            target,
            icon,
            "現在のビルドターゲット");

        return new MainToolbarLabel(content);
    }
}

ラベルはクリック不可の表示専用要素です。値を動的に更新する方法は後述のMainToolbar.Refreshで扱います。

複数要素をまとめて登録する

関連する複数の要素を1つのstaticメソッドで登録したい場合、戻り値をIEnumerable<MainToolbarElement>にしてyield returnで返します。

using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Toolbars;
using UnityEngine;

public static class BuildToolbarGroup
{
    [MainToolbarElement("Build/Group",
        defaultDockPosition = MainToolbarDockPosition.Right)]
    public static IEnumerable<MainToolbarElement> CreateElements()
    {
        // Windows用ビルドボタン
        yield return new MainToolbarButton(
            new MainToolbarContent("Win",
                EditorGUIUtility.IconContent("BuildSettings.Standalone").image as Texture2D,
                "Windows用にビルド"),
            () => BuildFor(BuildTarget.StandaloneWindows64));

        // Mac用ビルドボタン
        yield return new MainToolbarButton(
            new MainToolbarContent("Mac",
                EditorGUIUtility.IconContent("BuildSettings.Standalone").image as Texture2D,
                "Mac用にビルド"),
            () => BuildFor(BuildTarget.StandaloneOSX));
    }

    private static void BuildFor(BuildTarget target)
    {
        Debug.Log($"{target}用のビルドを開始します");
        // 実際のビルド処理(省略)
    }
}

動的な更新(MainToolbar.Refresh)

要素の表示内容を更新したい場合、MainToolbar.Refresh(elementPath)を呼び出します。これにより、該当パスのstaticメソッドが再度呼ばれて要素が作り直されます。

現在選択中の項目をボタンのテキストに反映する例を示します。

using UnityEditor;
using UnityEditor.Toolbars;
using UnityEngine;

public static class SelectedItemDropdown
{
    private const string ElementPath = "Examples/SelectedItemDropdown";
    private static string _selectedItem = "Item 1";

    [MainToolbarElement(ElementPath,
        defaultDockPosition = MainToolbarDockPosition.Middle)]
    public static MainToolbarElement Create()
    {
        // 現在の選択内容をテキストとして表示
        var content = new MainToolbarContent(_selectedItem);
        return new MainToolbarDropdown(content, ShowMenu);
    }

    private static void ShowMenu(Rect rect)
    {
        var menu = new GenericMenu();

        menu.AddItem(new GUIContent("Item 1"), _selectedItem == "Item 1", () =>
        {
            _selectedItem = "Item 1";
            // 表示を更新するためにRefreshを呼ぶ
            MainToolbar.Refresh(ElementPath);
        });
        menu.AddItem(new GUIContent("Item 2"), _selectedItem == "Item 2", () =>
        {
            _selectedItem = "Item 2";
            MainToolbar.Refresh(ElementPath);
        });

        menu.DropDown(rect);
    }
}

MainToolbar.Refreshは該当パスの要素だけを再生成するため、頻繁に呼んでもパフォーマンスへの影響は限定的です。

ただし、毎フレーム呼ぶような使い方は避けてください。

注意点

Unity 6.3未満では使用できない

MainToolbarElement系のAPIはUnity 6.3で追加された新機能のため、それ以前のバージョンではコンパイルエラーになります。

古いバージョンとの互換性が必要な場合は、バージョンディレクティブで囲みます。

#if UNITY_6000_3_OR_NEWER
[MainToolbarElement("Examples/MyButton")]
public static MainToolbarElement Create()
{
    // 実装
}
#endif

属性を付けるのはstaticメソッドのみ

[MainToolbarElement]staticメソッド専用です。インスタンスメソッドやクラスに付けても認識されません。

また、メソッドはドメインリロードのたびに呼び出される可能性があるため、状態はstaticフィールドかEditorPrefsなどの永続化領域に保持してください。メソッド内でローカル変数にだけ状態を持つと、Refreshのたびに初期化されてしまいます。

要素の表示/非表示はユーザーがカスタマイズできる

メインツールバーを右クリックしてCustomizeを開くと、登録した要素の表示/非表示や並び順をユーザーが変更できます。

defaultDockPositiondefaultDockIndexはあくまで初期値であり、ユーザー設定で上書きされる点に注意してください。

パフォーマンスへの影響

staticメソッドは必要なタイミングで呼び出されるだけですが、要素のクリックハンドラ内で重い処理を実行するとエディタが固まります。

時間のかかる処理はEditorApplication.delayCallなどで非同期化するか、別ウィンドウに処理を委譲することを検討してください。

まとめ

MainToolbarElementAPIは、これまで変更することが少し難しかったメインツールバーのカスタマイズをやっと公式でサポートしてくれた機能です。

使い方をまとめておくと、

  • [MainToolbarElement(path, defaultDockPosition = ...)]staticメソッドに付ける
  • staticメソッドからMainToolbarButton / MainToolbarDropdown / MainToolbarToggle / MainToolbarSlider / MainToolbarLabelを返す
  • 表示を更新したいときはMainToolbar.Refresh(path)を呼ぶ

これだけ。意外とシンプル。

個人的には、シーン切り替えドロップダウンが一番便利でした。

毎回ProjectウィンドウからAssetsフォルダを辿るのが地味に面倒だったので、これをメインツールバーに置いたら体感で作業速度がだいぶ変わります。Unity 6.3以降を触る機会があれば、一度試してみてください。