【Unity】AdvancedDropdownの使い方

#概要

備忘録として、UnityEditorの機能であるAdvancedDropdownの使い方を記事にしておきます。
エディター拡張については詳しく説明しません。

今回は、AssetPathを表示、選択できるDropdownを作ります。

#AdvancedDropdownとは
AdvancedDropdownとはUnityEditorの機能で、ゲームオブジェクトのインスペクタに表示されているAddcomponentボタンを押したときに表示されるDropdownと同じものです。

#1.AdvancedDropdownを表示するためのクラスを作成
Sample.csとSampleEditor.csを作成します。
また、SampleEditorはEditor/~に作成します。

```c#
public class Sample : MonoBehaviour
{
}
```

```c#
[CustomEditor(typeof(Sample))]
public class SampleEditor : Editor
{
}
```



#2.AdvancedDropdownを継承したクラスを作成
すべてのアセットパスをDropdownとして表示する機能を作成します。
AdvacnedDropdownを使うには、UnityEditor.IMGUI.Controlsをusingします。

```c#

public class AssetPathDropdown : AdvancedDropdown
{
    private static readonly float MIN_LINE_COUNT = 15.0f;
    public event Action<string> onItemSelected = null;
    private Dictionary<int, string> pathDictionary = null;

    public AssetPathDropdown(AdvancedDropdownState state) : base(state)
    {
        pathDictionary = new Dictionary<int, string>();
        
        // 最小サイズを設定
        var minimumSize = this.minimumSize;
        minimumSize.y = MIN_LINE_COUNT * EditorGUIUtility.singleLineHeight;
        this.minimumSize = minimumSize;
    }

    protected override AdvancedDropdownItem BuildRoot()
    {
        var root = new AdvancedDropdownItem("AssetPath");

        foreach (var path in AssetDatabase.GetAllAssetPaths())
        {
            var splitStrings = path.Split('/');
            var parent = root;
            AdvancedDropdownItem lastItem = null;

            foreach (var str in splitStrings)
            {
                var foundChildItem = parent.children.FirstOrDefault(item => item.name == str);

                if (foundChildItem != null)
                {
                    // すでに同じ名前のAdvancedDropdownItemがあった場合は次へ
                    parent = foundChildItem;
                    lastItem = foundChildItem;
                    continue;
                }

                var child = new AdvancedDropdownItem(str);
                parent.AddChild(child);

                parent = child;
                lastItem = child;
            }

            if (lastItem != null)
            {
                // KeyをAdvacnedDropdownItemnのidにしてパスを取得できるようにする
                pathDictionary[lastItem.id] = path;
            }
        }

        return root;
    }

    protected override void ItemSelected(AdvancedDropdownItem item)
    {
        base.ItemSelected(item);
        onItemSelected?.Invoke(pathDictionary[item.id]);
    }
}
```

#3.SampleEditor.csを編集
Display dropdown buttonを押したときにDropdownを表示するようにします。そして、Dropdownの要素を選択したときにアセットパスをDebug.Logで出力するようにします。

AdvancedDropdownは内部的に表示されるので、IMGUIですがIMGUIContainerを使用しません。

```c#
[CustomEditor(typeof(Sample))]
public class SampleEditor : Editor
{
    private static readonly float BUTTON_WIDTH = 225.0f;
    private AssetPathDropdown sampleDropdown = null;

    public override VisualElement CreateInspectorGUI()
    {
        var root = new VisualElement();
        var button = new Button()
        {
            text = "Display Dropdown"
        };
        
        // ボタンの表示方法を設定
        button.style.width = BUTTON_WIDTH;
        button.style.alignSelf = Align.Center;
        button.style.color = Color.white;
        root.Add(button);

        sampleDropdown = new AssetPathDropdown(new AdvancedDropdownState());

        // イベント購読
        sampleDropdown.onItemSelected += OnItemSelected;
        button.clicked += () => OnClick(button.worldBound);

        return root;
    }

    private void OnClick(Rect rect)
    {
        sampleDropdown.Show(rect);
    }

    private void OnItemSelected(string path)
    {
        Debug.Log(path);
    }
}
```

#完成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546425/5faa65b2-ca37-d382-0677-da4df286be4a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546425/93bc6446-3a3f-812d-7b6b-a4c8d6d79eec.png)

##コード
```c#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine.UIElements;

public class AssetPathDropdown : AdvancedDropdown
{
    private static readonly float MIN_LINE_COUNT = 15.0f;
    public event Action<string> onItemSelected = null;
    private Dictionary<int, string> pathDictionary = null;

    public AssetPathDropdown(AdvancedDropdownState state) : base(state)
    {
        pathDictionary = new Dictionary<int, string>();

        var minimumSize = this.minimumSize;
        minimumSize.y = MIN_LINE_COUNT * EditorGUIUtility.singleLineHeight;
        this.minimumSize = minimumSize;
    }

    protected override AdvancedDropdownItem BuildRoot()
    {
        var root = new AdvancedDropdownItem("AssetPath");

        foreach (var path in AssetDatabase.GetAllAssetPaths())
        {
            var splitStrings = path.Split('/');
            var parent = root;
            AdvancedDropdownItem lastItem = null;

            foreach (var str in splitStrings)
            {
                var foundChildItem = parent.children.FirstOrDefault(item => item.name == str);

                if (foundChildItem != null)
                {
                    parent = foundChildItem;
                    lastItem = foundChildItem;
                    continue;
                }

                var child = new AdvancedDropdownItem(str);
                parent.AddChild(child);

                parent = child;
                lastItem = child;
            }

            if (lastItem != null)
            {
                pathDictionary[lastItem.id] = path;
            }
        }

        return root;
    }

    protected override void ItemSelected(AdvancedDropdownItem item)
    {
        base.ItemSelected(item);
        onItemSelected?.Invoke(pathDictionary[item.id]);
    }
}

[CustomEditor(typeof(Sample))]
public class SampleEditor : Editor
{
    private static readonly float BUTTON_WIDTH = 225.0f;
    private AssetPathDropdown sampleDropdown = null;

    public override VisualElement CreateInspectorGUI()
    {
        var root = new VisualElement();
        var button = new Button()
        {
            text = "Display Dropdown"
        };

        button.style.width = BUTTON_WIDTH;
        button.style.alignSelf = Align.Center;
        button.style.color = Color.white;
        root.Add(button);

        sampleDropdown = new AssetPathDropdown(new AdvancedDropdownState());

        sampleDropdown.onItemSelected += OnItemSelected;
        button.clicked += () => OnClick(button.worldBound);

        return root;
    }

    private void OnClick(Rect rect)
    {
        sampleDropdown.Show(rect);
    }

    private void OnItemSelected(string path)
    {
        Debug.Log(path);
    }
}
```

#さいごに
この機能はUnityCsReferenceをみていたら偶然見つけました。あまり記事がないのと使い勝手が良さそうなので、備忘録として残しておきます。