【Unity：エディタ拡張】エディタでデータリスト表示をしてみよう

[グレンジ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/grenge) 20日目の記事担当kenjiです
クライアントエンジニアやってます

前にこんな記事を書いたんですが
[【Unity：エディタ拡張】Unityでエディタ拡張を始めよう](https://qiita.com/shirasaya0201/items/ee32f35ad3caac428368)
そこからもう少しエディタ拡張（主にレイアウトについて）に触れてみようと思います

実用的かは別として…
僕がエディタでデータリストを管理するときによくやる手法を書きます
やり方は色々ありますが、極力シンプルな設計でやってみます

Unityバージョン：2020.1.x

## **1. EditorWindowスクリプトの作成**
とりあえずエディタウィンドウ作成します

```csharp:EditorWindowSample.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class EditorWindowSample : EditorWindow
{
    [MenuItem("Editor/Sample")]
    private static void Create()
    {
        GetWindow<EditorWindowSample>("サンプル");
    }
}
```

Unityの上部メニュー「Editor > Sample」からウィンドウが開きます
![qiita_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216261/3c67532d-9e9f-59bb-3006-9eda2b28a26d.png)

## 2. **ScriptableObjectスクリプトの作成**
次にデータ用のスクリプトを作ります
パラメータは適当です

```csharp:ScriptableObjectSample.cs
using System;
using UnityEngine;

[Serializable]
public class ScriptableObjectSample : ScriptableObject
{
	[SerializeField]
	private int _sampleIntValue;
	[SerializeField]
	private float _sampleFloatValue;
	[SerializeField]
	private string _sampleStringValue;
}
```

## 3. **レイアウトの骨組み**
今回はレイアウトをスコープで２つに分割します
左にリスト、右に選択したデータのパラメータ
という感じでやってみます

エディタウィンドウのスクリプトに下記を追記します

```csharp:EditorWindowSample.cs
public class EditorWindowSample : EditorWindow
{
	private Vector2 _dataScrollPosition;
	private Vector2 _parameterScrollPosition;

	...

	private void OnGUI()
	{
		using (new GUILayout.HorizontalScope())
		{
			UpdateLayoutData();
			UpdateLayoutParameter();
		}
	}

	private void UpdateLayoutData()
	{
		using (GUILayout.ScrollViewScope scroll = new GUILayout.ScrollViewScope(_dataScrollPosition, EditorStyles.helpBox, GUILayout.Width(150)))
		{
			_dataScrollPosition = scroll.scrollPosition;

			GUILayout.Label("data");
		}
	}
	private void UpdateLayoutParameter()
	{
		using (GUILayout.ScrollViewScope scroll = new GUILayout.ScrollViewScope(_parameterScrollPosition, EditorStyles.helpBox))
		{
			_parameterScrollPosition = scroll.scrollPosition;

			GUILayout.Label("parameter");
		}
	}
}
```

この時点でこんな感じのレイアウトになります
分かりやすいように一旦ラベル表示してます
![qiita_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216261/52b2c1b5-5690-30a0-2ff2-8194a38f1e6b.png)

## 4. **データリストの表示**
あとは各レイアウトを固めていくだけです
最初にデータリストからやってみます
仮で追加してたラベル表示は消しちゃってください

```csharp:EditorWindowSample.cs
...

public class EditorWindowSample : EditorWindow
{
    private List<ScriptableObjectSample> _samples = new List<ScriptableObjectSample>();
    private ScriptableObjectSample _selectSample = null;

    ...
    
    private void UpdateLayoutData()
    {
        using (GUILayout.ScrollViewScope scroll = new GUILayout.ScrollViewScope(_dataScrollPosition, EditorStyles.helpBox, GUILayout.Width(150)))
        {
            _dataScrollPosition = scroll.scrollPosition;

            // メニュー追加
            GenericMenu menu = new GenericMenu();
            if (Event.current.type == EventType.ContextClick && Event.current.button == 1)
            {
                menu.AddItem(new GUIContent("AddSample"), false, () =>
                {
                    _samples.Add(CreateInstance<ScriptableObjectSample>());
                    _selectSample = _samples[_samples.Count - 1];
                });
            }

            // データリスト
            for (int i = 0; i < _samples.Count; i++)
            {
                GUI.backgroundColor = (_samples[i] == _selectSample ? Color.cyan : Color.white);
                if (GUILayout.Button($"{i}:sample"))
                {
                    _selectSample = _samples[i];
                }
                GUI.backgroundColor = Color.white;
            }

            // メニュー表示
            if (menu.GetItemCount() > 0)
            {
                menu.ShowAsContext();
                Event.current.Use();
            }
        }
    }
}
```

今回は右クリックからのメニュー追加をやってみました
データレイアウト内で右クリックして「AddSample」を選択するとデータが追加されるかと思います
これだけでもエディタ感でますよね
![qiita_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216261/93dd2a32-c403-a456-b17f-e56222112435.png)


## 5. **パラメータの表示**
次はパラメータの表示です
ここでは選択中のデータのパラメータを表示します

```csharp:EditorWindowSample.cs
...

public class EditorWindowSample : EditorWindow
{
    private void UpdateLayoutParameter()
    {
        using (GUILayout.ScrollViewScope scroll = new GUILayout.ScrollViewScope(_parameterScrollPosition, EditorStyles.helpBox))
        {
            _parameterScrollPosition = scroll.scrollPosition;

            if (_selectSample)
			{
				Editor.CreateEditor(_selectSample).DrawDefaultInspector();
			}
		}
	}
}
```

単純にデータのデフォルトインスペクター呼ぶだけです
簡単でした

![qiita_004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216261/d947c290-8c29-135e-e5be-914590b9885a.png)

## **おまけ**
ここまでやってきてデータの追加はできるんですけど
削除できないじゃんってなるので削除も右クリックメニューでできるようにしてみます
ボタン押したときの処理に右クリックだった場合の処理を追加します

```csharp:EditorWindowSample.cs
...

public class EditorWindowSample : EditorWindow
{
    ...
    
    private void UpdateLayoutData()
    {
        ...
            if (GUILayout.Button($"{i}:sample"))
            {
                _selectSample = _samples[i];

                // メニュー追加
                if (Event.current.button == 1)
                {
                    int index = i;
                    menu.AddItem(new GUIContent("Remove"), false, () =>
                    {
                        _samples.Remove(_samples[index]);
                        _selectSample = (_selectSample == _samples[index] ? null : _selectSample);
                    });
                }
            }
        ...
    }
```

追加の場合と基本同じですね
![qiita_005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216261/9a6b6c22-ed14-59c3-2fa9-d64c318e11e6.png)

## **まとめ**

今回はシンプルな例でしたが、エディタ拡張って機能追加や使いやすさを突き詰めていくと複雑な設計になったり可読性が悪くなったりするんですよね
地道なんですが、ハマる人にはハマる作業だと思うので
ちょっと面白いかもっと思った方はこれを機にもっと機能拡張をしてみてはどうでしょうか