yotiky Tech Blog

とあるエンジニアの備忘録

Unity - Editor 拡張のチートシート

目次

検証環境

  • Unity 2022.3.23f1

全般

リファレンス

GUIのユーティリティクラスの概要は以下の記事を参照。

yotiky.hatenablog.com

OnGUI

OnGUI: GUI イベントに応じて、フレームごとに複数回呼び出されます。レイアウトおよび再描画イベントが最初に処理され、その後にレイアウトおよびキーボード/マウスイベントが各入力イベントに対して処理されます。

イベント関数の実行順序 - Unity マニュアル

GUI イベントに応じて」とあるように、GUI関連のイベントが発生しないと呼び出されない。Updateのように毎フレーム必ず呼ばれるものではない。

イベント毎に1回呼び出されるため、OnGUIは1フレーム中に複数回呼び出される可能性がある。MonoBehaviourのenabledがfalseの時は呼ばれない。

Unity 起動時の処理

[InitializeOnLoad]
public class EditorInitializeSample
{
    static EditorInitializeSample()
    {
        // 起動時に実行したい処理
    }
}

InitializeOnLoad属性を付与したクラスで static コンストラクタを実装するとUnity起動直後に呼び出される。 または、以下のように呼び出したいメソッドにInitializeOnLoadMethod属性を付与しても呼び出される。

public class EditorInitializeSample
{
    [InitializeOnLoadMethod]
    private void Initizalie()
    {
        // 起動時に実行したい処理
    }
}

起動時エディタースクリプト実行 - Unity マニュアル

値の更新と保存

Unityでは、メモリ上のオブジェクトの状態とディスクに保存された値が常に同期されているわけではない。正しく保存しないと更新した内容が失われることになる。

詳細は値の更新とシリアライズを参照。

フォルダ構成

Editor フォルダ

Editor 拡張のスクリプトは「Editor フォルダ」配下に配置する。

複数のEditor フォルダを任意の場所に配置可能で、配置したスクリプトはビルドしたアプリ本体には含まれない。

リソース

Resources フォルダ

古くからあるアセットを配置するための特殊フォルダ。アプリのリソース管理としてはとっくに非推奨。

複数のResources フォルダを任意の場所に配置可能で、アプリ本体に含まれる。Editor フォルダの配下に置いた場合は、ビルドしたアプリ本体には含まれない。

Editor Default Resources フォルダ

エディタ用のリソースフォルダ。EditorGUIUtility.Loadを使用するとここからリソースを読み込む。

Assets直下に1つだけ作成できる。(Assets/Editor Default Resources) エディタ用のためアプリ本体には含まれない。

拡張対象

オリジナルウィンドウ

public class SampleWindow : EditorWindow
{
    [MenuItem("Tools/Sample Window")]
    static void OpenWindow()
    {
        GetWindow<SampleWindow >("Title");
    }

    private void Awake()
    {
        // 初期処理
    }

    private void OnGUI()
    {
        // 画面構成と処理
    }

EditorWindow を継承したクラスで、OnGUIの中で表示と処理を実装する。 呼び出し元ととしてメニューなどに項目を追加することが多い。

既存ウィンドウ

Inspector

[CustomEditor(typeof(SampleComponent))]
public class InspectorWindowSample : Editor
{
    private SampleComponent obj;

    private void OnEnable()
    {
        // 有効になった時に対象を確保しておく
        obj = target as SampleComponent;
    }

    public override void OnInspectorGUI()
    {
        // 画面構成と処理
        // 標準的な表示をしたい場合は、baseを呼び出す
        base.OnInspectorGUI()
    }
}

Editor を継承したクラスで、OnInspectorGUIの中で表示と処理を実装する。 CustomEditor属性で対象のコンポーネントを指定する必要がある。

Hierarchy

public class HierarchyWindowSample 
{
    [InitializeOnLoadMethod]
    private static void Initalize()
    {
        EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;
    }

    private static void HierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
    {
        // 表示の拡張や処理
    }
}

EditorApplication.hierarchyWindowItemOnGUIイベントにメソッドを登録してコールバックしてもらう。 登録処理が呼ばれるようにInitializeOnLoadもしくはInitializeOnLoadMethodを利用する。 HierarchyのGameObject毎に処理が呼ばれ、対象のGameObjectのインスタンスIDと描画領域の情報が受け取れる。

Project

public class ProjectWindowSample 
{
    [InitializeOnLoadMethod]
    private static void Initialize()
    {
        EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI;
    }

    private static void ProjectWindowItemOnGUI(string guid, Rect selectionRect)
    {
        // 表示の拡張や処理
    }
}

EditorApplication.projectWindowItemOnGUIイベントにメソッドを登録してコールバックしてもらう。 登録処理が呼ばれるようにInitializeOnLoadもしくはInitializeOnLoadMethodを利用する。 ProjectのAsset毎に処理が呼ばれ、対象のAssetのGUIDと描画領域の情報が受け取れる。

Scene

public class SceneWindowSample 
{
    [InitializeOnLoadMethod]
    private static void Initalize()
    {
        SceneView.duringSceneGui += SceneViewOnduringSceneGui;
    }

    private static void SceneViewOnduringSceneGui(SceneView view)
    {
        // 表示の拡張や処理
        // 3D GUIの描画はそのまま処理
        var obj = Selection.activeGameObject;
        Handles.Label(obj.transform.position + Vector3.up * 2, obj.transform.position.ToString());

        // 2D GUIの描画を拡張する場合は、BeginGUI/EndGUIで宣言する
        Handles.BeginGUI();
        // ここに2D GUIの表示や処理
        Handles.EndGUI();
    }
}

SceneView.duringSceneGuiイベントにメソッドを登録してコールバックしてもらう。 登録処理が呼ばれるようにInitializeOnLoadもしくはInitializeOnLoadMethodを利用する。 SceneViewは 3D handle GUI のため、2DのGUIを操作する時は、BeginGUIEndGUIで囲む必要がある。

もしくはEditor を継承したクラスで、OnSceneGUIの中で表示と処理を実装する方法もある。

[CustomEditor(typeof(SceneSampleComponent))]
public class SceneWindowSample : Editor 
{
    private SceneSampleComponent obj;

    private void OnEnable()
    {
        // 有効になった時に対象を確保しておく
        obj = target as SceneSampleComponent;
    }
    
    private void OnSceneGUI()
    {
        // 表示の拡張や処理
    }
}

CustomEditor属性で対象のコンポーネントを指定する必要がある。 そのため前者の方法は、SceneView自体のイベントに登録するので呼び出されるGameObjectに限定されないが、後者は基本的にはコンポーネントに限定されることになる。

オリジナルウィンドウ

ウィンドウを表示する

基本構成。

public class SampleWindow : EditorWindow
{
    [MenuItem("Tools/Sample Window")]
    static void OpenWindow()
    {
        GetWindow<SampleWindow >("Title");
    }

    private void Awake()
    {
        // 初期処理
    }

    private void OnGUI()
    {
        // 画面構成と処理
    }

EditorWindowShowXXXメソッドが定義されており、表示の仕方を選択できる。 ShowNotification はウィンドウではないがついでに。

メソッド フレームタイプ 表示位置 サイズ Modal 自動Close
Show タブ 前回 前回
ShowModal ダイアログ マウスカーソル 前回 o
ShowModalUtility ダイアログ ディスプレイ左上 既定 o
ShowUtility ダイアログ 前回 前回
ShowAuxWindow ダイアログ マウスカーソル 前回 非アクティブ
ShowAsDropDown None 指定必須 指定必須 非アクティブ
ShowPopup None ディスプレイ左上 既定
ShowNotification オーバーレイ Window中央やや下 5秒くらい

「前回」は、前回のサイズや位置を記憶しているもの。 「ディスプレイ左上」「既定」となっているものは、おそらくpositionを指定することを前提としているが必須ではないもの。記憶もしていないので毎回指定する必要がある。

以下、Showから順に位置やサイズを指定しない場合の表示。

Show

        var window = CreateInstance<ShowWindowSamplesWindow>();
        window.Show();

もしくは

        GetWindow<ShowWindowSamplesWindow>("Show");

GetWindowする場合は内部で呼ばれるのでShowを呼び出す必要はない。 更にウィンドウのインスタンス管理がされているため、既に開いている場合はそのウィンドウがアクティブになる。CreateInstanceの方は複数開く。

位置やサイズの指定

        var window = CreateInstance<ShowWindowSamplesWindow>();
        window.Show();
        window.minSize = new Vector2(100, 100);
        window.maxSize = new Vector2(1280, 960);
        // positionだけはShowの後じゃないと反映されない
        window.position = new Rect(20, 20, 400, 300);

positionは、前回の位置やサイズを記憶するウィンドウの場合Showした時に上書きされるため、Showより後に設定する必要がある。

アイコンを表示する(オリジナル)

        // original icon
        var icon = (Texture)EditorGUIUtility.Load("Icons/icon_x.png");
        window.titleContent = new GUIContent("X", icon);

タイトルとイメージを設定したGUIContentを、titleContentに設定する。
ここではEditor Default Resourcesフォルダからリソースを読み込んでいる。フォルダの詳細は別途記載。

タイトルと同じ名前のpngを「Assets/Editor Default Resources/Icons」の下に配置するとアイコンが読み込まれると書かれた記事もあるが、Unity 2022.3.23f1 時点では既に読み込まれなくなっている。

アイコンを表示する(ビルトイン)

        // builtin icon
        var builtinIcon = EditorGUIUtility.IconContent("BuildSettings.Xbox360");
        builtinIcon.text = "Xbox";
        window.titleContent = builtinIcon;

ビルトインのアイコンとは、Unity Editorで使われているアイコンのこと。 名前を指定してIconContentGUIContentを取得する。

        var icon = (Texture)EditorGUIUtility.Load("BuildSettings.Xbox360");

Textureとして扱いたい場合は、Loadで読める。

ビルトインのアイコンは、以下で一覧を作成してくれている。 しかし、2000個以上のアイコンがREADMEに記載されているため非常に重い。 また、4年前の更新で止まっているため、Unity 2020.1.0f1 までのアイコン。

GitHub - halak/unity-editor-icons

上記のフォークで、README.htmlで早く見れるようにしたリポジトリが以下にある。(プルリクしてるがマージされてない) クローンしてローカルでhtmlを開けば早く見れる。

GitHub - SixWays/unity-editor-icons

ShowModal

ウィンドウを閉じるまで他の画面で操作不可能。

        var window = CreateInstance<ShowWindowSamplesWindow>();
        window.ShowModal();

ShowModalUtility

ウィンドウを閉じるまで他の画面で操作不可能。ShowModalとは表示の位置やサイズなどに違いが見られる。

        var window = CreateInstance<ShowWindowSamplesWindow>();
        // positionはShowModalUtilityの前
        window.position = new Rect(50, 50, 400, 300);
        window.ShowModalUtility();

前回の位置やサイズを記憶するウィンドウではないので、positionShowModalUtilityより先に設定する必要がある。

位置はタイトルバーを除く要素の左上が起点となるため、yにディスプレイから見切れる値(0~タイトルバーなどの高さ)を設定した場合は、見きれない位置に調整される。

ShowUtility

ウィンドウを開いても他の画面を操作可能。ダイアログタイプなのでタブのドッキングなどはできない。

        var window = CreateInstance<ShowWindowSamplesWindow>();
        window.ShowUtility();

ShowAuxWindow

他の画面をクリックするなど、ウィンドウ自体が非アクティブになると閉じる。

        var window = CreateInstance<ShowWindowSamplesWindow>();
        window.ShowAuxWindow();

ShowAsDropDown

位置とサイズを指定してポップアップするフレームのないウィンドウ。他をクリックするなどウィンドウ自体が非アクティブになると閉じる。

    private Rect dropDownButtonRect;

    private void OnGUI()
    {
        if (GUILayout.Button("ShowAsDropDown"))
        {
            var popupPosition = GUIUtility.GUIToScreenPoint(dropDownButtonRect.center);
            var window = CreateInstance<PopupWindow>();
            window.ShowAsDropDown(new Rect(popupPosition, Vector2.zero), new Vector2(250, 150));
        }
        if (Event.current.type == EventType.Repaint) 
            dropDownButtonRect = GUILayoutUtility.GetLastRect();
    }

GUILayoutUtility.GetLastRect については後述。

ShowPopup

位置とサイズを指定してポップアップするフレームのないウィンドウ。非アクティブになっても閉じない。 タイトルバーもないため、明示的に閉じる処理を仕込む必要がある。

    private Rect popupButtonRect;

    private void OnGUI()
    {
        if (GUILayout.Button("ShowPopup"))
        {
            var popupPosition = GUIUtility.GUIToScreenPoint(popupButtonRect.center);
            var window = CreateInstance<PopupWindow>();
            window.position = new Rect(popupPosition, new Vector2(250, 150));
            window.ShowPopup();
        }
        if (Event.current.type == EventType.Repaint) 
            popupButtonRect = GUILayoutUtility.GetLastRect();
    }

GUILayoutUtility.GetLastRect については後述。

ShowNotification

ウィンドウにオーバーレイでメッセージを表示する。5秒程度で自動的に非表示になる。

    private void OnGUI()
    {
        if (GUILayout.Button("ShowNotification"))
        {
            ShowNotification(new GUIContent("ShowNotification"));
        }

        if (GUILayout.Button("Remove Notification"))
        {
            // 手動で消す場合
            RemoveNotification();
        }
    }

ウィンドウを閉じる

    var window = GetWindow<SampleWindow >("Title");
    window.Close();

自分自身なら単にthis.Close();でOK。

既定のウィンドウを開く

        var asm = Assembly.Load ("UnityEditor");
        var type = asm.GetType ("UnityEditor.BuildPlayerWindow");
        GetWindow(type);

yotiky.hatenablog.com

UIパーツ(GUILayout)

Label

        GUILayout.Label("Label");

TextField / TextArea / PasswordField

    private string textField = "";
    private string textArea = "";
    private string password = "";

    void OnGUI()
    {
        textField = GUILayout.TextField(textField);

        textArea = GUILayout.TextArea(textArea);

        password = GUILayout.PasswordField(password, '*');
    }

TextFieldにフォーカスがある状態で内部の変数を更新(ボタンクリックなど)しても画面に反映されないことがある。 その場合はフォーカスを外してあげる必要がある。GUI.FocusControlは変数を更新する前でも後でも問題ない。

        GUI.FocusControl("");
        text = "new value";

Toggle

    private bool toggle = false;

    void OnGUI()
    {
        toggle = GUILayout.Toggle(toggle, "Toggle");
    }

Button / RepeatButton

RepeatButtonは、クリックしている間ずっとtrueを返す。 ただしOnGUIで処理する場合は、GUIイベント時にしか呼び出されないので毎フレーム呼ばれるわけではない。

        if (GUILayout.Button("Button"))
            Debug.Log("Button clicked");

        if(GUILayout.RepeatButton("RepeatButton"))
            Debug.Log("RepeatButton clicking");

Toolbar

Toolbarは1行レイアウトの選択ボタン。選択したボタンのindexで判定する。

    private int toolbarSelected = 0;

    void OnGUI()
    {
        toolbarSelected = GUILayout.Toolbar(toolbarSelected, new[] { "Toolbar1", "Toolbar2", "Toolbar3" });
    }

SelectionGrid

SelectionGridは複数行レイアウトの選択ボタン。選択したボタンのindexで判定する。 引数のxCountで列数を指定する。HorizontalScopeでもVertizalScopeでも配置が水平方向なのは変わらない。

    private int selGridSelected = 0;

    void OnGUI()
    {
        selGridSelected = GUILayout.SelectionGrid(selGridSelected, new[] { "SelectionGrid1", "SelectionGrid2", "SelectionGrid3" }, 2);
    }

HorizontalSlider / VerticalSlider

引数は、leftValuerightValue。 VerticalSliderはleftValueが上、rightValueが下。

    private float hSliderValue = 0;
    private float vSliderValue = 0;

    void OnGUI()
    {
        hSliderValue = GUILayout.HorizontalSlider(hSliderValue, 0, 100, GUILayout.Height(20f));
        vSliderValue = GUILayout.VerticalSlider(vSliderValue, 100, 0, GUILayout.Height(50f));
    }

HorizontalScrollbar / VerticalScrollbar

HorizontalScrollbarの引数は、sizeleftValuerightValuesizeはスクロールボタンのサイズで、左右の値の差分に対する値で指定する。0~10でsizeが10なら目一杯のスクロールボタンになるためスクロールできない。

VerticalScrollbarの引数は、sizetopValuebottomValuetopValueが先。

    private float hSbarValue = 0;
    private float vSbarValue = 0;

    void OnGUI()
    {
        hSbarValue = GUILayout.HorizontalScrollbar(hSbarValue, 1, 0, 10);
        vSbarValue = GUILayout.VerticalScrollbar(vSbarValue, 1, 10, 0);
    }

UIパーツ(EditorGUILayout)

LabelField / SelectableLabel / PrefixLabel

SelectableLabel は選択可能なラベル。 PrefixLabelはクリックすると直後のコントロールにフォーカスが移動するラベル。

        EditorGUILayout.LabelField("Label");

        EditorGUILayout.SelectableLabel("SelectableLabel");

        EditorGUILayout.PrefixLabel("PrefixLabel");

TextField / DelayedTextField / TextArea / PasswordField

DelayedTextField は、Enterかフォーカスが外れるまで戻り値が変更されない入力コントロール

    private string textField = "";
    private string textArea = "";
    private string password = "";

    void OnGUI()
    {
        textField = EditorGUILayout.TextField(textField);

        textField = EditorGUILayout.DelayedTextField(textField);

        textArea = EditorGUILayout.TextArea(textArea);

        password = EditorGUILayout.PasswordField(password);
    }

Toggle / ToggleLeft

Toggleはラベルが左になる。ToggleLeftはトグルが左、ラベルが右になる。

    private bool toggle = false;

    void OnGUI()
    {
        toggle = EditorGUILayout.Toggle(toggle);
        toggle = EditorGUILayout.Toggle("ToggleRight", toggle);

        toggle = EditorGUILayout.ToggleLeft("ToggleLeft", toggle);
    }

ToggleGroupScope

配下のToggleを無効化する。チェックが付いている時に入力可能になる。

    private bool toggleGroup = false;
    private bool toggle = false;

    void OnGUI()
    {
        using (var scope = new EditorGUILayout.ToggleGroupScope("Toggle Group Scope", toggleGroup))
        {
            toggleGroup = scope.enabled;
            toggle = EditorGUILayout.Toggle("Toggle", toggle);
            toggle = EditorGUILayout.Toggle("Toggle", toggle);
            toggle = EditorGUILayout.Toggle("Toggle", toggle);
        }
    }

FloatField / IntField / LongField / DoubleField

FloatField以外は省略。

    private float floatField = 0;

    void OnGUI()
    {
        floatField = EditorGUILayout.FloatField(floatField);
    }

DelayedFloatField / DelayedIntField / DelayedDoubleField

Enterかフォーカスが外れるまで戻り値が変更されない入力コントロール。 DelayedFloatField以外は省略。

    private float floatField = 0;

    void OnGUI()
    {
        floatField = EditorGUILayout.DelayedFloatField(floatField);
    }

ObjectField

第3引数はallowSceneObjects

    private Object objectField = null;

    void OnGUI()
    {
        objectField = EditorGUILayout.ObjectField(objectField, typeof(Object), true);
    }

LinkButton

        if (EditorGUILayout.LinkButton("LinkButton"))
            Debug.Log("Button clicked");

Slider / IntSlider / MinMaxSlider

IntSlider以外はfloatを扱う。

    private float fSliderValue = 0;
    private int iSliderValue = 0;
    private float mmSliderMinLimit = -20;
    private float mmSliderMaxLimit = 20;
    private float mmSliderMinValue = -10;
    private float mmSliderMaxValue = 10;

    void OnGUI()
    {
        fSliderValue = EditorGUILayout.Slider("Slider", fSliderValue, 0, 10);

        iSliderValue = EditorGUILayout.IntSlider("IntSlider", iSliderValue, 0, 10);

        EditorGUILayout.MinMaxSlider("MinMaxSlider", ref mmSliderMinValue, ref mmSliderMaxValue, mmSliderMinLimit, mmSliderMaxLimit);
    }

Popupは、配列のIndexでプルダウンメニューを表示する。

IntPopupは、intの値を配列で渡すことで、選択したアイテムの値を返す。

EnumPopupは、enumのプルダウンメニューを表示する。表示名は変更できないため、日本語などに変換して選択できるようにする場合はPopupの方を使う。

    private int popupIndex;
    private int popupIntValue;
    private Fruit popupEnumValue;

    void OnGUI()
    {
        popupIndex = EditorGUILayout.Popup("Index", popupIndex, new[] { "りんご", "みかん", "パイナップル" });

        popupIntValue = EditorGUILayout.IntPopup("Int", popupIntValue, new[] { "十", "二十", "三十" }, new[] { 10, 20, 30 });

        popupEnumValue = (Fruit)EditorGUILayout.EnumPopup("Enum", popupEnumValue);
    }

DisabledScope

引数disabledtrueの時に配下のコントロールを操作不可にする。

    private bool disabled = true;

    void OnGUI()
    {
        using (new EditorGUI.DisabledScope(disabled))
        {
            EditorGUILayout.IntField("IntField", 0);
        }
    }

HelpBox

MessageType.None以外はアイコンが表示される。 引数widetrueの場合Windowの幅、falseの場合編集エリア(呼び方不明)の幅になる。 編集エリアはInspectorなどでラベルと編集のコントロールがセットになっている項目の編集側の領域を指す。 省略時はtrue

        EditorGUILayout.HelpBox("HelpBox None", MessageType.None);
        EditorGUILayout.HelpBox("HelpBox Info", MessageType.Info);
        EditorGUILayout.HelpBox("HelpBox Warning", MessageType.Warning);
        EditorGUILayout.HelpBox("HelpBox Error", MessageType.Error);

        EditorGUILayout.Toggle("Toggle", true);
        EditorGUILayout.HelpBox("HelpBox Error", MessageType.Error, false);

その他

今回扱っていないもの。

        // BoundsField
        // BoundsIntField
        // ColorField
        // CurveField
        // EnumFlagsField
        // GradientField
        // LayerField
        // MaskField
        // PropertyField
        // RectField
        // RectIntField
        // TagField
        // Vector2Field
        // Vector2IntField
        // Vector3Field
        // Vector3IntField
        // Vector4Field
        
        // DropdownButton
        // InspectorTitlebar

レイアウト関連

スペース

GUILayoutにもEditorGUILayoutにも、Spaceは定義されている。 GUILayoutの引数はpixel、EditorGUILayoutの引数はwidth。若干違いがあるっぽいので別物?

        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("Label1");
            GUILayout.Space(20f);
            GUILayout.Label("Label2");
            GUILayout.Space(20f);
            GUILayout.Label("Label3");
        }
        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("Label1");
            EditorGUILayout.Space(20f);
            GUILayout.Label("Label2");
            EditorGUILayout.Space(20f);
            GUILayout.Label("Label3");
        }

Box

テキスト、イメージ、それらを組み合わせたものなどを囲って表示できる。 LayoutOptionを指定しなければ中身にフィットする。
水平線(罫線)を引きたい時にも利用される。

            GUILayout.Box("Box");
            // 水平線
            GUILayout.Box("", GUILayout.Height(2), GUILayout.ExpandWidth(true));

垂直に配置する

GUILayoutにもEditorGUILayoutにも、VerticalScopeは定義されている。動きは同じに見える。 Labelのスタイルは若干違うっぽい。

        using (new GUILayout.VerticalScope())
        {
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
        }
        using (new GUILayout.VerticalScope())
        {
            EditorGUILayout.LabelField("Label1");
            EditorGUILayout.LabelField("Label2");
            EditorGUILayout.LabelField("Label3");
        }
        using (new EditorGUILayout.VerticalScope())
        {
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
        }
        using (new EditorGUILayout.VerticalScope())
        {
            EditorGUILayout.LabelField("Label1");
            EditorGUILayout.LabelField("Label2");
            EditorGUILayout.LabelField("Label3");
        }

水平に配置する

GUILayoutにもEditorGUILayoutにも、VerticalScopeは定義されている。動きは同じに見える。 EditorGUILayout.LabelFieldの方はMinWidthが設定されてるようで一定幅より小さくならない。

        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
        }
        using (new GUILayout.HorizontalScope())
        {
            EditorGUILayout.LabelField("Label1");
            EditorGUILayout.LabelField("Label2");
            EditorGUILayout.LabelField("Label3");
        }
        using (new EditorGUILayout.HorizontalScope())
        {
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
        }
        using (new EditorGUILayout.HorizontalScope())
        {
            EditorGUILayout.LabelField("Label1");
            EditorGUILayout.LabelField("Label2");
            EditorGUILayout.LabelField("Label3");
        }

一定幅より狭くなると見切れ始める。

インデント

        using (new EditorGUI.IndentLevelScope())
        {
            // 引数なしでindentLevel++と同じ、引数でレベルを指定できる、デフォルト(最上位)は0
            EditorGUILayout.LabelField("indentLevelScope");

            using (new EditorGUI.IndentLevelScope())
            {
                // 入れ子にするとさらにindentLevel++
                EditorGUILayout.LabelField("indentLevelScope");
            }
        }

usingを使わない以前の書き方。 GUILayout.Labelの方には影響しないみたい。

        // デフォルトは0レベル
        EditorGUILayout.LabelField("indentLevel = default");
        EditorGUI.indentLevel = 2;
        EditorGUILayout.LabelField("indentLevel = 2");
        EditorGUI.indentLevel = 0;
        EditorGUILayout.LabelField("indentLevel = 0");

        // インクリメントでもインデントできる
        EditorGUI.indentLevel++;
        EditorGUILayout.LabelField("indentLevel++");
        EditorGUI.indentLevel++;
        EditorGUILayout.LabelField("indentLevel++");
        // GUILayoutには影響しない
        GUILayout.Label("GUILayout is not effect");
        EditorGUI.indentLevel--;
        EditorGUILayout.LabelField("indentLevel--");
        EditorGUI.indentLevel--;
        EditorGUILayout.LabelField("indentLevel--");

Alignment

GUILayout.FlexibleSpace を利用する。FlexibleSpaceをいくつ、どこに置くか、で余白部分が自動で調整される。

        using (new GUILayout.HorizontalScope())
        {
            // 左寄せ
            GUILayout.Label("Left");
            GUILayout.Label("Right");
            GUILayout.FlexibleSpace();
        }
        using (new GUILayout.HorizontalScope())
        {
            // 両端寄せ
            GUILayout.Label("Left");
            GUILayout.FlexibleSpace();
            GUILayout.Label("Right");
        }
        using (new GUILayout.HorizontalScope())
        {
            // 右寄せ
            GUILayout.FlexibleSpace();
            GUILayout.Label("Left");
            GUILayout.Label("Right");
        }
        using (new GUILayout.HorizontalScope())
        {
            // 等間隔
            GUILayout.FlexibleSpace();
            GUILayout.Label("Left");
            GUILayout.FlexibleSpace();
            GUILayout.Label("Right");
            GUILayout.FlexibleSpace();
        }
        using (new GUILayout.HorizontalScope())
        {
            // 中央寄せ
            GUILayout.FlexibleSpace();
            GUILayout.Label("Left");
            GUILayout.Label("Right");
            GUILayout.FlexibleSpace();
        }
        using (new GUILayout.HorizontalScope())
        {
            // 中央やや左寄せ
            GUILayout.FlexibleSpace();
            GUILayout.Label("Left");
            GUILayout.Label("Right");
            GUILayout.FlexibleSpace();
            GUILayout.FlexibleSpace();
        }

折りたたむ

折りたたまれるだけでインデントはつかない。

    private bool foldout = false;

    void OnGUI()
    {
        foldout = EditorGUILayout.Foldout(foldout, "Foldout");
        if (foldout)
        {
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
        }
    }

スクロール

    private Vector2 scrollPosScope = Vector2.zero;

    void OnGUI()
    {
        using (var scope = new EditorGUILayout.ScrollViewScope(scrollPosScope, GUILayout.Width(120f), GUILayout.Height(60f)))
        {
            // options でサイズや最大サイズ、最小サイズなどを指定できる
            scrollPosScope = scope.scrollPosition;
            GUILayout.Label("Label1");
            GUILayout.Label("Label2");
            GUILayout.Label("Label3");
            GUILayout.Label("Label4");
            GUILayout.Label("Label5");
        }
    }

Layout Option

        GUILayout.Box("Box 100x100", GUILayout.Width(100f), GUILayout.Height(100f));
        GUILayout.Box(
            "Once upon a time, there lived an old couple in a small village. One day the old wife was washing her clothes in the river when a huge peach came tumbling down the stream.",
            GUILayout.MinWidth(100f), GUILayout.MinHeight(100f), GUILayout.MaxWidth(200f), GUILayout.MaxHeight(100f));
        GUILayout.Box("Box ExpandWidth", GUILayout.ExpandWidth(true));
        GUILayout.Box("Box ExpandHeight", GUILayout.ExpandHeight(true));

Boxの場合、最小サイズに収まりきらない場合はテキストが見切れる。ExpandはWindowのサイズに応じて、自動レイアウトの余白部分で広がる。余白がなければ広がらない。

スタイル

FontSize

LabelのデフォルトのFontSzieは12。GUIStyleの初期値の色は黒なので見えづらい。

        GUILayout.Label("FontSize:0(=12)", new GUIStyle{ fontSize = 0 });
        GUILayout.Label("FontSize:12", new GUIStyle{ fontSize = 12 });
        GUILayout.Label("FontSize:18", new GUIStyle{ fontSize = 18 });
        GUILayout.Label("FontSize:24", new GUIStyle{ fontSize = 24 });
        GUILayout.Label("FontSize:30", new GUIStyle{ fontSize = 30 });
        GUILayout.Label("FontSize:36", new GUIStyle{ fontSize = 36 });

FontStyle

        GUILayout.Label("FontStyle:Bold", new GUIStyle{ fontStyle = FontStyle.Bold });
        GUILayout.Label("FontStyle:Italic", new GUIStyle{ fontStyle = FontStyle.Italic });
        GUILayout.Label("FontStyle:BoldAndItalic", new GUIStyle{ fontStyle = FontStyle.BoldAndItalic });

Color

EditorStylesから既定のUIパーツで使われているスタイルを取得できる。

        using (new GUILayout.HorizontalScope())
        {
            // Labelのデフォルト色は (0.769,0.769,0.769)
            GUILayout.Label("Label");
            GUILayout.Label("Label", new GUIStyle(EditorStyles.label));
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(0.769f, 0.769f, 0.769f) } });
        }
        using (new GUILayout.HorizontalScope())
        {
            GUILayout.Label("boldLabel", new GUIStyle(EditorStyles.boldLabel));
            GUILayout.Label("largeLabel", new GUIStyle(EditorStyles.largeLabel));
            GUILayout.Label("linkLabel", new GUIStyle(EditorStyles.linkLabel));
            GUILayout.Label("miniButton", new GUIStyle(EditorStyles.miniButton));
            GUILayout.Label("popup", new GUIStyle(EditorStyles.popup));
        }

スタイルだけなのでボタンやポップアップは動かない。マウスホーバーで色が変わるくらい。

        using (new GUILayout.HorizontalScope())
        {
            // モノクロ
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(0, 0, 0) } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(0.25f, 0.25f, 0.25f) } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(0.5f, 0.5f, 0.5f) } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(0.75f, 0.75f, 0.75f) } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = new Color(1, 1, 1) } });
        }

        using (new GUILayout.HorizontalScope())
        {
            // 定義されたColor 1
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.black } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.grey } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.gray } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.clear } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.white } });
        }
        using (new GUILayout.HorizontalScope())
        {
            // 定義されたColor 2
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.red } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.green } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.blue } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.yellow } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.magenta } });
            GUILayout.Label("Label", new GUIStyle { normal = new GUIStyleState { textColor = Color.cyan } });
        }

greyとgrayは同じ色。

GUIStyle

GUIStyleが引数の場合、文字列でスタイル名を指定するとスタイルが適用される。

        GUILayout.Button("Button", "toolbarbutton");
        GUILayout.Button("Button", "ToolbarButtonFlat");
        GUILayout.Button("Button", "toolbarbuttonLeft");
        GUILayout.Button("Button", "toolbarbuttonRight");

GUI.skinに定義されたGUIStyleは以下の通り。 適用できるかどうかは試してみないとわからない。 量が多いので折りたたむ。

  • AboutWIndowLicenseLabel
  • AC BoldHeader
  • AC Button
  • AC ComponentButton
  • AC GroupButton
  • AC LeftArrow
  • AC PreviewHeader
  • AC PreviewText
  • AC RightArrow
  • AM ChannelStripHeaderStyle
  • AM EffectName
  • AM HeaderStyle
  • AM MixerHeader
  • AM MixerHeader2
  • AM ToolbarLabel
  • AM ToolbarObjectField
  • AM TotalVuLabel
  • AM VuValue
  • AnimationEventBackground
  • AnimationEventTooltip
  • AnimationEventTooltipArrow
  • AnimationKeyframeBackground
  • AnimationPlayHead
  • AnimationRowEven
  • AnimationRowOdd
  • AnimationSelectionTextField
  • AnimationTimelineTick
  • AnimClipToolbar
  • AnimClipToolbarButton
  • AnimClipToolbarPopup
  • AnimItemBackground
  • AnimLeftPaneSeparator
  • AnimPlayToolbar
  • AnimPropDropdown
  • AppCommand
  • AppCommandLeft
  • AppCommandLeftOn
  • AppCommandMid
  • AppCommandRight
  • AppToolbar
  • AppToolbarButtonLeft
  • AppToolbarButtonMid
  • AppToolbarButtonRight
  • ArrowNavigationLeft
  • ArrowNavigationRight
  • AssetLabel
  • AssetLabel Icon
  • AssetLabel Partial
  • AvatarMappingBox
  • AvatarMappingErrorLabel
  • AxisLabelNumberField
  • Badge
  • BoldLabel
  • BoldTextField
  • BoldToggle
  • BottomShadowInwards
  • box
  • BreadcrumbsSeparator
  • button
  • ButtonLeft
  • ButtonMid
  • ButtonRight
  • BypassToggle
  • CacheFolderLocation
  • CenteredLabel
  • ChannelStripAttenuationBar
  • ChannelStripAttenuationMarkerSquare
  • ChannelStripBg
  • ChannelStripDuckingMarker
  • ChannelStripEffectBar
  • ChannelStripSendReturnBar
  • ChannelStripVUMeterBg
  • CircularToggle
  • CN Box
  • CN CenteredText
  • CN CountBadge
  • CN EntryBackEven
  • CN EntryBackOdd
  • CN EntryError
  • CN EntryErrorIcon
  • CN EntryErrorIconSmall
  • CN EntryErrorSmall
  • CN EntryInfo
  • CN EntryInfoIcon
  • CN EntryInfoIconSmall
  • CN EntryInfoSmall
  • CN EntryWarn
  • CN EntryWarnIcon
  • CN EntryWarnIconSmall
  • CN EntryWarnSmall
  • CN Message
  • CN StacktraceBackground
  • CN StacktraceStyle
  • CN StatusError
  • CN StatusInfo
  • CN StatusWarn
  • ColorField
  • ColorPicker2DThumb
  • ColorPickerBackground
  • ColorPickerBox
  • ColorPickerCurrentColor
  • ColorPickerCurrentExposureSwatchBorder
  • ColorPickerExposureSwatch
  • ColorPickerHorizThumb
  • ColorPickerHueRing
  • ColorPickerHueRing HDR
  • ColorPickerHueRingThumb
  • ColorPickerOriginalColor
  • ColorPickerSliderBackground
  • Command
  • CommandLeft
  • CommandMid
  • CommandRight
  • ContentToolbar
  • ControlHighlight
  • ControlLabel
  • CurveEditorBackground
  • CurveEditorLabelTickmarks
  • CurveEditorLabelTickmarksOverflow
  • CurveEditorRightAlignedLabel
  • DD Background
  • DD HeaderStyle
  • DD ItemCheckmark
  • DD ItemStyle
  • DD LargeItemStyle
  • DefaultCenteredLargeText
  • DefaultCenteredText
  • DefaultLineSeparator
  • dockarea
  • dockareaOverlay
  • dockareaStandalone
  • dockHeader
  • DopesheetBackground
  • Dopesheetkeyframe
  • DopesheetRippleLeft
  • DopesheetRippleRight
  • DopesheetScaleLeft
  • DopesheetScaleRight
  • dragtab
  • dragtab first
  • dragtab scroller next
  • dragtab scroller prev
  • dragtabdropwindow
  • DropDown
  • DropDownButton
  • DropDownToggleButton
  • DropzoneStyle
  • EditModeSingleButton
  • ErrorLabel
  • ExposablePopupItem
  • ExposablePopupMenu
  • EyeDropperHorizontalLine
  • EyeDropperPickedPixel
  • EyeDropperVerticalLine
  • FloatFieldLinkButton
  • flow background
  • flow node 0
  • flow node 0 on
  • flow node 1
  • flow node 1 on
  • flow node 2
  • flow node 2 on
  • flow node 3
  • flow node 3 on
  • flow node 4
  • flow node 4 on
  • flow node 5
  • flow node 5 on
  • flow node 6
  • flow node 6 on
  • flow node base
  • flow node hex 0
  • flow node hex 0 on
  • flow node hex 1
  • flow node hex 1 on
  • flow node hex 2
  • flow node hex 2 on
  • flow node hex 3
  • flow node hex 3 on
  • flow node hex 4
  • flow node hex 4 on
  • flow node hex 5
  • flow node hex 5 on
  • flow node hex 6
  • flow node hex 6 on
  • flow node hex base
  • flow node titlebar
  • flow target in
  • flow triggerPin in
  • flow triggerPin out
  • flow varPin in
  • flow varPin out
  • flow varPin tooltip
  • Foldout
  • FoldoutHeader
  • FoldoutHeaderIcon
  • FoldOutPreDrop
  • Frame
  • FrameBox
  • GameViewBackground
  • Grad Down Swatch
  • Grad Down Swatch Overlay
  • Grad Up Swatch
  • Grad Up Swatch Overlay
  • grey_border
  • GridList
  • GridListText
  • GroupBox
  • GUIEditor.BreadcrumbLeft
  • GUIEditor.BreadcrumbLeftBackground
  • GUIEditor.BreadcrumbMid
  • GUIEditor.BreadcrumbMidBackground
  • GV Gizmo DropDown
  • HeaderButton
  • HeaderLabel
  • HelpBox
  • Hi Label
  • HorizontalMinMaxScrollbarThumb
  • horizontalscrollbar
  • horizontalscrollbarleftbutton
  • horizontalscrollbarrightbutton
  • horizontalscrollbarthumb
  • horizontalslider
  • horizontalsliderthumb
  • HorizontalSliderThumbExtent
  • hostview
  • HoverHighlight
  • IconButton
  • IN BigTitle
  • IN BigTitle Inner
  • IN BigTitle Post
  • IN CenteredLabel
  • IN DropDown
  • IN EditColliderButton
  • IN Foldout
  • IN Footer
  • IN Label
  • IN LockButton
  • IN MinMaxStateDropDown
  • IN ObjectField
  • IN TextField
  • IN ThumbnailSelection
  • IN ThumbnailShadow
  • IN Title
  • IN Title Flat
  • IN TitleText
  • IN TypeSelection
  • InnerShadowBg
  • InsertionMarker
  • InvisibleButton
  • label
  • LargeBoldLabel
  • LargeButton
  • LargeButtonLeft
  • LargeButtonMid
  • LargeButtonRight
  • LargeLabel
  • LightmapEditorSelectedHighlight
  • LinkLabel
  • LODBlackBox
  • LODCameraLine
  • LODLevelNotifyText
  • LODRendererAddButton
  • LODRendererButton
  • LODRendererRemove
  • LODRenderersText
  • LODSceneText
  • LODSliderBG
  • LODSliderRange
  • LODSliderRangeSelected
  • LODSliderText
  • LODSliderTextSelected
  • MeBlendBackground
  • MeBlendPosition
  • MeBlendTriangleLeft
  • MeBlendTriangleRight
  • MeLivePlayBackground
  • MeLivePlayBar
  • MenuItem
  • MenuItemMixed
  • MenuToggleItem
  • MeTimeBlockLeft
  • MeTimeBlockRight
  • MeTimeLabel
  • MeTransitionBack
  • MeTransitionBlock
  • MeTransitionHandleLeft
  • MeTransitionHandleLeftPrev
  • MeTransitionHandleRight
  • MeTransitionHead
  • MeTransitionSelect
  • MeTransitionSelectHead
  • MeTransOff2On
  • MeTransOffLeft
  • MeTransOffRight
  • MeTransOn2Off
  • MeTransOnLeft
  • MeTransOnRight
  • MeTransPlayhead
  • MiniBoldLabel
  • minibutton
  • minibuttonleft
  • minibuttonmid
  • minibuttonright
  • MiniLabel
  • MiniMinMaxSliderHorizontal
  • MiniMinMaxSliderVertical
  • MiniPopup
  • MiniPullDown
  • MiniSliderHorizontal
  • MiniSliderVertical
  • MiniTextField
  • MiniToolbarButton
  • MiniToolbarButtonLeft
  • MinMaxHorizontalSliderThumb
  • MultiColumnArrow
  • MultiColumnHeader
  • MultiColumnHeaderCenter
  • MultiColumnHeaderRight
  • MultiColumnTopBar
  • MuteToggle
  • NotificationBackground
  • NotificationText
  • ObjectField
  • ObjectFieldButton
  • ObjectFieldMiniThumb
  • ObjectFieldThumb
  • ObjectFieldThumbLightmapPreviewOverlay
  • ObjectFieldThumbOverlay
  • ObjectFieldThumbOverlay2
  • ObjectPickerBackground
  • ObjectPickerLargeStatus
  • ObjectPickerPreviewBackground
  • ObjectPickerResultsEven
  • ObjectPickerResultsGrid
  • ObjectPickerResultsOdd
  • ObjectPickerSmallStatus
  • ObjectPickerTab
  • ObjectPickerToolbar
  • OffsetDropDown
  • OL box
  • OL box flat
  • OL box NoExpand
  • OL EntryBackEven
  • OL EntryBackOdd
  • OL Label
  • OL MiniPing
  • OL MiniRenameField
  • OL Minus
  • OL Ping
  • OL Plus
  • OL ResultFocusMarker
  • OL ResultLabel
  • OL RightLabel
  • OL SelectedRow
  • OL Title
  • OL Title TextRight
  • OL Toggle
  • OL ToggleMixed
  • OL ToggleWhite
  • OT BottomBar
  • OT TopBar
  • OverrideMargin
  • PaneOptions
  • PlayerSettingsLevel
  • PlayerSettingsPlatform
  • Popup
  • PopupCurveDropdown
  • PopupCurveEditorBackground
  • PopupCurveEditorSwatch
  • PopupCurveSwatchBackground
  • PR BrokenPrefabLabel
  • PR DisabledBrokenPrefabLabel
  • PR DisabledLabel
  • PR DisabledPrefabLabel
  • PR Insertion
  • PR Label
  • PR Ping
  • PR PrefabLabel
  • PR TextField
  • PreBackground
  • PreBackgroundSolid
  • PreButton
  • PreButtonBlue
  • PreButtonGreen
  • PreButtonRed
  • PreDropDown
  • PreferencesKeysElement
  • PreferencesSection
  • PreferencesSectionBox
  • PrefixLabel
  • PreHorizontalScrollbar
  • PreHorizontalScrollbarThumb
  • PreLabel
  • PreLabelUpper
  • PreMiniLabel
  • PreOverlayLabel
  • PreSlider
  • PreSliderThumb
  • PreToolbar
  • PreToolbar2
  • PreVerticalScrollbar
  • PreVerticalScrollbarThumb
  • PreviewPackageInUse
  • ProfilerBadge
  • ProfilerDetailViewBackground
  • ProfilerGraphBackground
  • ProfilerHeaderLabel
  • ProfilerLeftPane
  • ProfilerNoDataAvailable
  • ProfilerNotSupportedWarningLabel
  • ProfilerPaneSubLabel
  • ProfilerRightPane
  • ProfilerScrollviewBackground
  • ProfilerSelectedLabel
  • ProfilerTimelineBar
  • ProfilerTimelineDigDownArrow
  • ProfilerTimelineFoldout
  • ProfilerTimelineLeftPane
  • ProfilerTimelineRollUpArrow
  • ProgressBarBack
  • ProgressBarBar
  • ProgressBarText
  • ProjectBrowserBottomBarBg
  • ProjectBrowserGridLabel
  • ProjectBrowserHeaderBgMiddle
  • ProjectBrowserHeaderBgTop
  • ProjectBrowserIconAreaBg
  • ProjectBrowserIconDropShadow
  • ProjectBrowserPreviewBg
  • ProjectBrowserSubAssetBg
  • ProjectBrowserSubAssetBgCloseEnded
  • ProjectBrowserSubAssetBgDivider
  • ProjectBrowserSubAssetBgMiddle
  • ProjectBrowserSubAssetBgOpenEnded
  • ProjectBrowserSubAssetExpandBtn
  • ProjectBrowserSubAssetExpandBtnMedium
  • ProjectBrowserSubAssetExpandBtnSmall
  • ProjectBrowserTextureIconDropShadow
  • ProjectBrowserTopBarBg
  • QualitySettingsDefault
  • quick search tab
  • Radio
  • RectangleToolHBar
  • RectangleToolHBarLeft
  • RectangleToolHBarRight
  • RectangleToolHighlight
  • RectangleToolRippleLeft
  • RectangleToolRippleRight
  • RectangleToolScaleBottom
  • RectangleToolScaleLeft
  • RectangleToolScaleRight
  • RectangleToolScaleTop
  • RectangleToolSelection
  • RectangleToolVBar
  • RectangleToolVBarBottom
  • RectangleToolVBarTop
  • RegionBg
  • ReorderableList
  • ReorderableListRightAligned
  • RightAlignedLabel
  • RightLabel
  • RL Background
  • RL DragHandle
  • RL Element
  • RL Empty Header
  • RL Footer
  • RL FooterButton
  • RL Header
  • SC ViewAxisLabel
  • SC ViewLabel
  • SC ViewLabelCentered
  • SC ViewLabelLeftAligned
  • SceneTopBarBg
  • SceneViewOverlayTransparentBackground
  • SceneVisibility
  • ScriptText
  • scrollview
  • ScrollViewAlt
  • SearchCancelButton
  • SearchCancelButtonEmpty
  • SearchModeFilter
  • SearchTextField
  • SelectionRect
  • SettingsHeader
  • SettingsIconButton
  • SettingsListItem
  • SettingsTreeItem
  • ShurikenCheckMark
  • ShurikenCheckMarkMixed
  • ShurikenDropdown
  • ShurikenEditableLabel
  • ShurikenEffectBg
  • ShurikenEmitterTitle
  • ShurikenLabel
  • ShurikenMinus
  • ShurikenModuleBg
  • ShurikenModuleTitle
  • ShurikenObjectField
  • ShurikenPlus
  • ShurikenPopup
  • ShurikenToggle
  • ShurikenToggleMixed
  • ShurikenValue
  • SliderMixed
  • SoloToggle
  • StaticDropdown
  • StatusBarIcon
  • sv_iconselector_back
  • sv_iconselector_button
  • sv_iconselector_labelselection
  • sv_iconselector_selection
  • sv_iconselector_sep
  • sv_label_0
  • sv_label_1
  • sv_label_2
  • sv_label_3
  • sv_label_4
  • sv_label_5
  • sv_label_6
  • sv_label_7
  • Tab first
  • Tab last
  • Tab middle
  • Tab onlyOne
  • TabWindowBackground
  • Tag MenuItem
  • TE BoxBackground
  • TE DefaultTime
  • TE DropField
  • TE ElementBackground
  • TE NodeBackground
  • TE NodeBox
  • TE NodeBoxSelected
  • TE NodeLabelBot
  • TE NodeLabelTop
  • TE PinLabel
  • TE Toolbar
  • TE toolbarbutton
  • TE ToolbarDropDown
  • textarea
  • textfield
  • TextFieldDropDown
  • TextFieldDropDownText
  • TimeAreaToolbar
  • TimeRulerBackground
  • TimeScrubber
  • TimeScrubberButton
  • Titlebar Foldout
  • TL InPoint
  • TL OutPoint
  • TL Playhead
  • toggle
  • ToggleMixed
  • Toolbar
  • ToolbarBoldLabel
  • ToolbarBottom
  • toolbarbutton
  • ToolbarButtonFlat
  • toolbarbuttonLeft
  • toolbarbuttonRight
  • ToolbarCreateAddNewDropDown
  • ToolbarDropDown
  • ToolbarDropDownLeft
  • ToolbarDropDownRight
  • ToolbarDropDownToggle
  • ToolbarDropDownToggleButton
  • ToolbarDropDownToggleRight
  • ToolbarLabel
  • ToolbarPopup
  • ToolbarPopupLeft
  • ToolbarPopupRight
  • ToolbarSearchCancelButton
  • ToolbarSearchCancelButtonEmpty
  • ToolbarSearchCancelButtonWithJump
  • ToolbarSearchCancelButtonWithJumpEmpty
  • ToolbarSearchField
  • ToolbarSearchTextField
  • ToolbarSearchTextFieldJumpButton
  • ToolbarSearchTextFieldPopup
  • ToolbarSearchTextFieldWithJump
  • ToolbarSearchTextFieldWithJumpPopup
  • ToolbarSearchTextFieldWithJumpPopupSynced
  • ToolbarSearchTextFieldWithJumpSynced
  • ToolbarSlider
  • ToolbarSliderTextField
  • ToolbarTextField
  • Tooltip
  • TV Insertion
  • TV InsertionRelativeToSibling
  • TV Line
  • TV LineBold
  • TV Ping
  • TV Selection
  • U2D.createRect
  • U2D.dragDot
  • U2D.dragDotActive
  • U2D.dragDotDimmed
  • U2D.pivotDot
  • U2D.pivotDotActive
  • VerticalMinMaxScrollbarThumb
  • verticalscrollbar
  • verticalscrollbardownbutton
  • verticalscrollbarthumb
  • verticalscrollbarupbutton
  • verticalslider
  • verticalsliderthumb
  • VerticalSliderThumbExtent
  • VideoClipImporterLabel
  • WarningOverlay
  • WhiteBackground
  • WhiteBoldLabel
  • WhiteLabel
  • WhiteLargeCenterLabel
  • WhiteLargeLabel
  • WhiteMiniLabel
  • WinBtn
  • WinBtnClose
  • WinBtnCloseMac
  • WinBtnInactiveMac
  • WinBtnMax
  • WinBtnMaxMac
  • WinBtnMinMac
  • WinBtnRestore
  • WinBtnRestoreMac
  • window
  • WindowBottomResize
  • Wizard Box
  • Wizard Error
  • WordWrapLabel
  • wordwrapminibutton
  • WordWrappedLabel
  • WordWrappedMiniLabel

EditorStyles

GUIStyleのコンストラクタに渡すと既定のスタイルを引き継いだ新しいスタイルを生成できる。

            var newStyle = new GUIStyle(EditorStyles.label));

EditorStylesに定義されたスタイルは以下の通り。

  • Font
    • boldFont
    • miniBoldFont
    • miniFont
    • standardFont
  • Control
    • boldLabel
    • centeredGreyMiniLabel
    • colorField
    • foldout
    • foldoutHeader
    • foldoutHeaderIcon
    • foldoutPreDrop
    • helpBox
    • iconButton
    • inspectorDefaultMargins
    • inspectorFullWidthMargins
    • label
    • largeLabel
    • layerMaskField
    • linkLabel
    • miniBoldLabel
    • miniButton
    • miniButtonLeft
    • miniButtonMid
    • miniButtonRight
    • miniLabel
    • miniPullDown
    • miniTextField
    • numberField
    • objectField
    • objectFieldMiniThumb
    • objectFieldThumb
    • popup
    • radioButton
    • selectionRect
    • structHeadingLabel
    • textArea
    • textField
    • toggle
    • toggleGroup
    • toolbar
    • toolbarButton
    • toolbarDropDown
    • toolbarPopup
    • toolbarSearchField
    • toolbarTextField
    • whiteBoldLabel
    • whiteLabel
    • whiteLargeLabel
    • whiteMiniLabel
    • wordWrappedLabel
    • wordWrappedMiniLabel

HorizontalScope / VerticalScope にスタイルを適用する

HorizontalScope や VerticalScope の引数にGUIStyleを渡すことができるので、配下のコントロールをBox等で囲むことができる。

        using (new GUILayout.HorizontalScope("Box"))
        {
            GUILayout.Label("Box1");
            GUILayout.Label("Box2");
            GUILayout.Label("Box3");
            GUILayout.Button("Box4");
        }
        using (new GUILayout.HorizontalScope("HelpBox"))
        {
            GUILayout.Label("HelpBox1");
            GUILayout.Label("HelpBox2");
            GUILayout.Label("HelpBox3");
            GUILayout.Button("HelpBox4");
        }

ラジオボタン

1行であればToolbar、複数行であればSelectionGridにEditorStyles.radioButtonを設定する。

        toolbarSelected = GUILayout.Toolbar(toolbarSelected, new[] { "Toolbar1", "Toolbar2", "Toolbar3" }, EditorStyles.radioButton);
        selGridSelected = GUILayout.SelectionGrid(selGridSelected, new[] { "SelectionGrid1", "SelectionGrid2", "SelectionGrid3" }, 2, EditorStyles.radioButton);

スクリプトのシリアライゼーション

スクリプトのシリアライゼーションは、MonoBehaviourやScriptableObjectの状態(SerializeFieldやpublic field等)をUnityが保存して、再起動後でも永続的に状態を復元できるようにするための仕組み。

Sceneファイル(.unity)やScriptableObjectファイル(.asset)がシリアライズされたファイルで、YAML形式で保存されている。

SerializedObject

SerializedObject は、シリアル化されたスクリプトを編集するための汎用的な方法を提供する。 UnityEditorに定義されているのでEditor拡張用のクラス。

使いそうな場面

  • MonoBehaviour
    • HierarchyウィンドウのGameObjectから
    • Hierarchyで選択したGameObjectのInspectorウィンドウから
    • Hierarchyで選択したGameObjectのメニューから
  • ScriptableObject
    • Projectで選択したScriptableObjectのInspectorウィンドウから

SerializedObject - Unity スクリプトリファレンス

スクリプトのシリアル化 - Unity マニュアル

生成

SerializedObjectはコンストラクタの引数にObjectを持つ。配列で複数渡すことも可能。 Selectionを使用して現在選択されているGameObjectを利用できる。

            // by Selection.activeGameObject
            var obj = Selection.activeGameObject;
            if (obj == null) return;

            var component = obj.GetComponent<SerializedObjectSampleComponent>();
            if (component == null) return;

            var so = new SerializedObject(component);

            // by Selection.gameObjects
            var transforms = Selection.gameObjects.Select(go => go.transform).ToArray();
            so = new SerializedObject(transforms);

Editor クラスを継承した拡張の場合、targetserializedObjectを利用できる。

            // by target
            var so = new SerializedObject(target);

            // by serializedObject
            so = serializedObject;

ScriptableObjectを直接与えることもできる。 また元のオブジェクトはtargetObjectから取得できる。

            // by ScriptableObject
            var scriptableObject = ScriptableObject.CreateInstance<SampleScriptableObject>();
            var so = new SerializedObject(scriptableObject);
            var to = so.targetObject as SampleScriptableObject;

プロパティの編集

    [MenuItem("Edit/Reset Selected Objects Position")]
    static void ResetPosition()
    {
        var transforms = Selection.gameObjects.Select(go => go.transform).ToArray();
        var so = new SerializedObject(transforms);
        // メモリ上の値を最新化(インスタンスが複数のフレームをまたぐ場合に必要)
        so.Update();
        // 更新
        so.FindProperty("m_LocalPosition").vector3Value = Vector3.zero;
        // 更新内容を反映する(シリアライズ)
        so.ApplyModifiedProperties();
    }

SerializedObjectインスタンスが複数のフレームに跨る場合は、内容を更新する前に必ずUpdateが必要となる。
Updateでシリアル化されたファイルから最新の値を読み込む。Updateを呼ばないと別の場所で同時に編集されていた場合にどちらかの内容が欠損する。
本来、上記コードではUpdateは不要と思われるが、忘れるより常に呼び出すくらいでも良さそう。

内容の更新が終わったらApplyModifiedPropertiesを呼び出してストリーム内の変更をフラッシュ(SerializedObjectに渡したオブジェクトに反映)する。

SerializedObjectへの変更はそのままでは元のオブジェクトへは反映されずキャッシュされるだけなので、ApplyModifiedPropertiesを呼ぶことで反映させる。
この際、Unityがディスクに保存しているファイルがある場合はファイルが更新される。

Inspectorウィンドウでプロパティを右クリックするとメニューに「Copy Property Path」が表示されるので、実行するとメンバ変数名(m_LocalPosition)をコピーできる。
もしくは、Shift+右クリックすると「Print Property Path」が表示されるので実行するとConsoleに出力される。

Inspectorウィンドウを拡張している場合は、EditorGUILayout.PropertyFieldを使って表示している場合にメニューが表示される。

EditorGUILayout.PropertyFieldを使うとデフォルトの入力欄が表示される。カスタマイズする場合は個別にUIパーツで調整する。

        // デフォルトの入力欄を表示
        EditorGUILayout.PropertyField(so.FindProperty("boolValue"));

        // カスタマイズしたい場合は個別に
        so.FindProperty("boolValue").boolValue = EditorGUILayout.Toggle("Bool Value", so.FindProperty("boolValue").boolValue, "CircularToggle");

Editor クラスを継承した拡張(OnInspectorGUIOnSceneGUI)を行う場合、CustomEditor属性で指定したコンポーネントにストリームを提供するserializedObjectプロパティが定義されている。

[CustomEditor(typeof(SampleComponent))]
public class InspectorWindowSample : Editor
{
    public override void OnInspectorGUI()
    {
        // baseの実装では、UpdateとApplyModifiedPropertiesを処理している
        //base.OnInspectorGUI();

        serializedObject.Update();
        // serializedObjectの値を更新
        serializedObject.ApplyModifiedProperties();
    }
}

base.OnInspectorGUIの実装ではUpdateとApplyが処理されている。
自前の実装する際は、フレームを跨いでインスタンスが存続するため、UpdateApplyModifiedPropertiesを呼ぶのを忘れないようにする。

SerializedObject - Unity スクリプトリファレンス

プリミティブ型

    [SerializeField] private int intValue;
        serializedObject.FindProperty("intValue").intValue = 10;

基本型にはアクセッサー代わりのメンバ変数が用意されている。

プリミティブ以外も含まれるが、プロパティの値にアクセスするために用意されているメンバ変数。

intValue 整数のプロパティーの値
longValue Long としての整数のプロパティーの値
boolValue boolean プロパティーの値
doubleValue double として float プロパティーの値
floatValue float プロパティーの値
stringValue string プロパティーの値
colorValue color プロパティーの値
vector2Value Vector2 プロパティーの値
vector3Value Vector3 プロパティーの値
vector4Value Vector4 プロパティーの値
vector2IntValue Value of a 2D integer vector property.
vector3IntValue Value of a 3D integer vector property.
rectValue Rect プロパティーの値
rectIntValue Value of a rectangle with integer values property.
boundsValue Bounds プロパティーの値
boundsIntValue Value of bounds with integer values property.
quaternionValue Quaternion プロパティーの値
animationCurveValue アニメーションカーブのプロパティーの値
objectReferenceValue 参照オブジェクトのプロパティーの値
exposedReferenceValue A reference to another Object in the Scene. This reference is resolved in the context of the SerializedObject containing the SerializedProperty.

リスト

    [SerializeField] private List<int> list = new List<int>();
        var listProperty = serializedObject.FindProperty("list");
        if (GUILayout.Button("Add"))
        {
            listProperty.InsertArrayElementAtIndex(listProperty.arraySize);
        }

        if (GUILayout.Button("Remove"))
        {
            if (listProperty.arraySize > 0)
                listProperty.DeleteArrayElementAtIndex(listProperty.arraySize - 1);
        }

        if (GUILayout.Button("Clear"))
        {
            listProperty.ClearArray();
        }

enum

    public enum Fruit
    {
        Apple,
        Orange,
        GoldenPeach,
    }
    [SerializeField] private Fruit enumValue;
        var enumValueProperty = serializedObject.FindProperty("enumValue");
        enumValueProperty.enumValueIndex = 
            EditorGUILayout.Popup("フルーツ", enumValueProperty.enumValueIndex, enumValueProperty.enumDisplayNames);

SerializedPropertyには、enumNamesenumDisplayNamesが定義されている。前者はenumの名前をそのまま、後者はスペースと大文字でフォーマットされる。

GoldenPeach Golden Peach

オブジェクトの参照

    [SerializeField] private GameObject objectReferenceValue;
        // GameObject
        var objRef = serializedObject.FindProperty("objectReferenceValue").objectReferenceValue as GameObject;
        if (objRef == null) return;

        var soRef = new SerializedObject(objRef.transform);
        soRef.FindProperty("m_LocalPosition").vector3Value = Vector3.up * 3;

        // ScriptableObject
        var scriptableObject = serializedObject.FindProperty("scriptableObject").objectReferenceValue as SampleScriptableObject;
        var soSo = new SerializedObject(scriptableObject);

他のGameObject(コンポーネント)などを参照するプロパティの場合はobjectReferenceValueで取得できる。 ScriptableObjectも同様に扱える。 ただし、objectReferenceValueはObject型のためピュアC#のクラスなどは取得できない。

参照型のプロパティ

public class SerializedObjectSampleComponent : MonoBehaviour
{
    [Serializable]
    public class SubClass
    {
        public double doubleValue;
    }
    
    [SerializeField] private SubClass subClassReference;
}
        var objRefProperty = serializedObject.FindProperty("subClassReference");
        var relativeProperty = objRefProperty.FindPropertyRelative("doubleValue");
        relativeProperty.doubleValue++;

        // これでもOK
        var relativeProperty1 = serializedObject.FindProperty("subClassReference.doubleValue");

FindPropertyRelativeでプロパティのプロパティを辿れる。もしくはドットで繋いだパスの指定も可能。

ScriptableObject

        // 新しいインスタンス
        var scriptableObject = ScriptableObject.CreateInstance<SampleScriptableObject>();
        var so1 = new SerializedObject(scriptableObject);
        so1.FindProperty("text").stringValue = "piyopiyo";
        so1.ApplyModifiedProperties();

        // Resourcesから読み込む
        var scriptableObject = Resources.Load<SampleScriptableObject>("Sample1");
        var so2 = new SerializedObject(scriptableObject);

        // GameObjectのフィールドから取得する
        var scriptableObject = serializedObject.FindProperty("scriptableObject").objectReferenceValue as SampleScriptableObject;
        var so3 = new SerializedObject(scriptableObject);

新たに読み込んだScriptableObjectは、更新を反映するのにApplyModifiedPropertiesを呼ぶ必要がある。

値の更新とシリアライズ

Unity(メモリ)上のSceneやPrefabなどの更新と、シリアライズされたファイル(.unityや.prefab)は常に同期されているわけではない。 適宜保存しないと変更が失われることはよくある。

EditorUtility.SetDirtyは、指定したアセットがAssetDatabaseによる再シリアル化が必要になったことを伝える。

Undo機構を利用すると、「元に戻す状態」がスタックに記録されるようになり、SetDirtyを呼ばなくても変更が通知される。

Editorクラスが持つserializedObjectに関しては、Undo、SetDirtyは気にしなくてもよしなにやってくれてそう。

この辺は、同じフレーム内での処理を想定していると思われるので、非同期処理など複数フレームに及ぶ処理を実装すると危険な気配がある。

SetDirty

    public override void OnInspectorGUI()
    {
        var obj = (ObjectModifySampleComponent)target;
        obj.StringValue = EditorGUILayout.TextField("Text", obj.StringValue);
        EditorUtility.SetDirty(obj);
    }

serializedObjectではなくtargetを使ったこのコードだと、 EditorUtility.SetDirtyを呼ばないと変更はシリアライズされないため、シーンを開き直すなどすると内容が失われる。 EditorUtility.SetDirtyを呼ぶことでAssetDatabaseに更新があることを通知する。

SetDirtyは通知するだけなので、実際に何も更新していなくても呼び出せば「変更がある状態」を示す。

        if (GUILayout.Button("SetDirty"))
        {
            EditorUtility.SetDirty(obj);
        }

Prefabの場合、SetDirtyを呼ばないと変更が検知されない。

SetDirtyを呼べば自動で保存(シリアライズ)され、変更も検知されるようになる。

``

保存まで行う場合はAssetDatabaseのメソッドを使う。

    // すべて
    AssetDatabase.SaveAssets();
    // 指定したアセット
    AssetDatabase.SaveAssetIfDirty(obj);

ちなみに、古いリファレンスだと非推奨と書かれているが最新のリファレンスだと非推奨とまでは書かれてなかったりする。

変更の検知

    public override void OnInspectorGUI()
    {
        EditorGUI.BeginChangeCheck();

        var obj = (ObjectModifySampleComponent)target;
        obj.StringValue = EditorGUILayout.TextField("Text", obj.StringValue);

        if(EditorGUI.EndChangeCheck())
        {
            EditorUtility.SetDirty(obj);
        }
    }

EditorGUI.BeginChangeCheckEditorGUI.EndChangeCheckで囲むと、GUIでの変更の検知ができる。 BeginChangeCheckは、内部的にはGUI.changedをスタックしてfalseを設定し直している。 EndChangeCheckでスタックから取り出して判定している。

Undo

        var obj = (ObjectModifySampleComponent)target;
        Undo.RecordObject(obj, "Value change");
        obj.intValue++;
        obj.StringValue = "hoge";

注意する点は、対象のオブジェクトを更新する前に呼び出すこと。

Undo.RecordObjectを使うと、オブジェクトの状態の一時コピーが作成され、フレームの最後で状態を比較して更新を検知してくれる。 変更内容は、元に戻すスタックに記録されていく。

上記のコードの場合、2つの変数が一度で元に戻る。 第2引数のnameは、メニューバーに表示される。

「Undo History」を開くとスタックされたUndoが一覧で見れる。選択するとその状態まで行ったり来たりできる。

Undo.RecordObjectの他にUndo.RegisterCompleteObjectUndoも存在する。 RecordObjectは変更点を記録し、RegisterCompleteObjectUndoはUndo スタック上のオブジェクトの状態の複製を保存するとあるが違いはよくわからない。

RegisterCompleteObjectUndoの引数は、「Undo される必要のある状態が変わっているオブジェクト」とあるが、RecordObjectと同じ呼び出し順で同じ挙動をするので謎。

        Undo.RecordObject(obj, "Value change");
        obj.intValue++;
        obj.StringValue = "hoge";

        PrefabUtility.RecordPrefabInstancePropertyModifications(obj);

Prefabのインスタンスが対象の場合、PrefabUtility.RecordPrefabInstancePropertyModificationsを呼ばないと変更が失われると注意書きがされている。 いくつか試してみたが、RecordObjectだけでも変更が失われなかったため本当に必要かどうかは不明。頭の片隅に覚えておくと良い。 PrefabUtility.RecordPrefabInstancePropertyModifications単体で呼び出しても何も起きなかった。

Undoの種類

前述のUndoはプロパティの変更を対象としている。GameObjectの追加や、コンポーネントの追加や削除などは専用のメソッドが用意されている。

Modify a single property:

Undo.RecordObject (myGameObject.transform, "Zero Transform Position");
myGameObject.transform.position = Vector3.zero;

Add a component:

Undo.AddComponent<Rigidbody>(myGameObject);

Create a new GameObject:

var go = new GameObject();
Undo.RegisterCreatedObjectUndo (go, "Created go");

Destroy a GameObject or component:

Undo.DestroyObjectImmediate (myGameObject);

その他リファレンスを参照。

docs.unity3d.com

設定の保存

保存する

    private string saveDataPath = "Assets/SamplesWindowData.asset";

    void Main()
    {
        var data = ScriptableObject.CreateInstance<SampleScriptableObject>();
        // dataを更新
        SaveData(data);
    }

    private void SaveData(SampleScriptableObject data)
    {
        if (!AssetDatabase.Contains(data))
        {
            AssetDatabase.CreateAsset(data, saveDataPath );
        }

        data.hideFlags = HideFlags.NotEditable; // Inspectorから編集不可
        EditorUtility.SetDirty(data);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

読み込む

    private string saveDataPath = "Assets/SamplesWindowData.asset";

    private SampleScriptableObject LoadData()
    {
        return AssetDatabase.LoadAssetAtPath<SampleScriptableObject >(saveDataPath );
    }

「No script asset for XXX. Check that the definition is in a file of the same name and that it compiles properly.」が発生した場合

ScriptableObjectのクラス名とファイル名が一致していない場合に発生する。

より詳細には、クラス名と同じ名前のファイルが存在していれば、クラスの定義がそのファイルに書かれていなくても警告がでなくなる。 しかし使い所は特になく、同じ名前のファイルが必要なことに変わりはないのでそこに定義しておけば良い。

No script asset for SampleScriptableObject. Check that the definition is in a file of the same name and that it compiles properly.

EditorUtility

EditorUtility - Unity スクリプトリファレンス

ダイアログ

            if (GUILayout.Button("Dialog"))
            {
                // okならtrue, cancelならfalse
                var result = EditorUtility.DisplayDialog("Title", "Message.", "OK Label", "Cancel Label");
                Debug.Log(result + " selected.");
            }
            if (GUILayout.Button("DialogComplex"))
            {
                // okなら0, cancelなら1, altなら2
                var result = EditorUtility.DisplayDialogComplex("Title", "Message.", "OK Label", "Cancel Label", "3rd button");
                Debug.Log(result + " selected.");
            }

フォルダ選択ダイアログ

    private string selectedPath;

    private OnGUI()
    {
        if (GUILayout.Button("FolderPanel", GUILayout.Width(100f)))
        {
            selectedPath = EditorUtility.OpenFolderPanel("Target folder", Application.dataPath, string.Empty);
        }
    }

ファイル選択ダイアログ

    private string selectedPath;

    private OnGUI()
    {
            if (GUILayout.Button("FilePanel", GUILayout.Width(100f)))
            {
                selectedPath = EditorUtility.OpenFilePanel("Target file", Application.dataPath, string.Empty);
            }
            if (GUILayout.Button("FilePanelWithFilters", GUILayout.Width(150f)))
            {
                selectedPath = EditorUtility.OpenFilePanelWithFilters("Target file", Application.dataPath, new []{ "Image files", "png,jpg,jpeg", "All files", "*" });
            }
    }

第3引数で拡張子を指定できる。Emptyの場合は「All files」。

複数指定する場合は、OpenFilePanelWithFiltersの方を使う。

フォルダ保存ダイアログ

フォルダ選択と同じ。

    private string selectedPath;

    private OnGUI()
    {
        if (GUILayout.Button("FolderPanel", GUILayout.Width(100f)))
        {
                selectedPath = EditorUtility.SaveFolderPanel("Target folder", Application.dataPath, string.Empty);
        }
    }

ファイル保存ダイアログ

    private string selectedPath;

    private OnGUI()
    {
            if (GUILayout.Button("FilePanel", GUILayout.Width(100f)))
            {
                selectedPath = EditorUtility.SaveFilePanel("Target file", Application.dataPath, "Default name", "txt");
            }
            if (GUILayout.Button("FilePanelInProject", GUILayout.Width(150f)))
            {
                selectedPath = EditorUtility.SaveFilePanelInProject("Target file", "DefaultName", "txt", "message");
            }
    }

SaveFilePanelInProjectを使用するとAssets/からの相対パスで取得できる。 Windowsだと残念ながらmessage は表示されない。

PopupMenu

Projectウィンドウの右クリックメニューと同じようなことができる。 menuItemPath で表示するメニューのパスを指定する。

    private Rect popupButtonRect;

    private OnGUI()
    {
        if (GUILayout.Button("PopupMenu", GUILayout.Width(100f)))
        {
            var popupPosition = GUIUtility.GUIToScreenPoint(popupButtonRect.center);
            EditorUtility.DisplayPopupMenu(new Rect(popupPosition, Vector2.zero), "Assets/Create", null);
        }
        if (Event.current.type == EventType.Repaint) 
            popupButtonRect = GUILayoutUtility.GetLastRect();
    }

プログレスバー

終了後に必ずClearProgressBar を呼ばないとプログレスバーが消えないため、使う時は try ~ finally などの対策が必要。

    private int guiEventCount = -1;
    private bool cancelablePBar;

    private OnGUI()
    {
        if (guiEventCount is >= 0 and <= 20)
        {
            if (cancelablePBar)
                EditorUtility.DisplayCancelableProgressBar("Title", "Message", (float)guiEventCount / 20);
            else
                EditorUtility.DisplayProgressBar("Title", "Message", (float)guiEventCount / 20);
            
            guiEventCount++;
        }
        else if (guiEventCount > 20)
        {
            EditorUtility.ClearProgressBar();
            guiEventCount = -1;
        }
        else
        {
            if (GUILayout.Button("Progressbar"))
            {
                guiEventCount = 0;
                cancelablePBar = false;
            }
            if (GUILayout.Button("CancelableProgressBar"))
            {
                guiEventCount = 0;
                cancelablePBar = true;
            }
        }
    }

その他Tips

OnGUIを毎フレーム実行する

Repaintを呼び出すとGUIイベントが発生するので、OnGUIを強制的に呼び出すことができる。

    void Update()
    {
        Repaint();
    }

マウスの座標を取得する

Repaintで毎フレーム実行して、Event.current.mousePositionを取得する。 取得できる座標は、ウィンドウの左上(0, 0)を起点とした相対座標になる。

    void Update()
    {
        Repaint();
    }
    void OnGUI()
    {
        GUILayout.Label(Event.current.mousePosition.ToString());
    }

コントロールの位置とサイズを取得する

GUILayoutUtility.GetLastRectを使うと直前のコントロールのRectを取得できる。 Repaintイベント時にしか使えないので判定を入れておく必要がある。

        GUILayout.Button("Target control");
        if (Event.current.type == EventType.Repaint) 
            Debug.Log(GUILayoutUtility.GetLastRect().ToString());

サンプルプロジェクト

github.com

関連記事