Unityのエディタ拡張でListを折りたたみ表示する

#概要
UnityのListのインスペクタを自分で実装しようとした時のメモです。
出来るだけUnityの標準インスペクタに近づけつつ、AddとDeleteのボタンで管理できるようにしました。

#準備

```C#:status.cs
public class Status
{
    public string name = "";
    public int hp = 0;
    public int mp = 0;

    public Status(string name, int hp, int mp)
    {
        this.name = name;
        this.hp = hp;
        this.mp = mp;
    }
}
```

```C#:ListController.cs
using System.Collections.Generic;
using UnityEngine;

public class ListController : MonoBehaviour 
{

    public List<Status> list = new List<Status>();

	// Use this for initialization
    void Start () 
    {
        list.Add(new Status("戦士", 100, 100));
        list.Add(new Status("魔法使い", 80, 150));
        list.Add(new Status("遊び人", 50, 50));
	}
}

```

Listの要素であるStatusクラスと
Listを監理するListController

```C#:ListControllerEditor.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ListController))] 
public class ListControllerEditor : Editor 
{
    // Listの折りたたみ用の変数
    bool folding_list = false;
    // Listの要素の折りたたみ用の配列
    bool[] foldings;

    public override void OnInspectorGUI()
    {
        // tagetでListControllerを取得
        ListController ctrl = target as ListController;

        var list = ctrl.list;
        foldings = new bool[list.Count];

        // Listを折りたたみ表示
        if (folding_list = EditorGUILayout.Foldout(folding_list, "List"))
        {
            // インデントを増やす
            EditorGUI.indentLevel++;

            for(int i = 0; i < list.Count; i++)
            {
                // インデントを増やす
                EditorGUI.indentLevel++;

                // Listの要素を折りたたみ表示
                if (foldings[i] = EditorGUILayout.Foldout(foldings[i], "Status_" + i))
                {
                    list[i].name = EditorGUILayout.TextField("Name", list[i].name);
                    list[i].hp = EditorGUILayout.IntField("HP", list[i].hp);
                    list[i].mp = EditorGUILayout.IntField("MP", list[i].mp);
                }

                // インデントを減らす
                EditorGUI.indentLevel--;
            }

            // Listの追加
            if (GUILayout.Button("Add"))
            {
                list.Add(new Status("", 0, 0));
            }

            // インデントを減らす
            EditorGUI.indentLevel--;
        }
    }
}
```

ListCntrollerのインスペクタを拡張するクラス
これはEditorフォルダに入れないと正しくコンパイルされないので注意。

#中身が展開されない問題
とりあえず実行してみると、Statusをクリックして見ても中身が展開されない。。。

![スクリーンショット 2018-01-23 11.53.50.png](https://qiita-image-store.s3.amazonaws.com/0/106605/cb08d80b-121b-bc41-84d2-43053e735f8b.png)

初歩的なミスなんですが、C#だとbool配列は初期化すると中身は全てfalseが入ります。
OnInspectorGUIはタイミングはわからないですが、開いている間繰り返し実行されるので、
その度にfalseが入り、開けない状態でした...

以下、修正版です。

```C#:ListControllerEditor.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ListController))] 
public class ListControllerEditor : Editor 
{
    bool isInitialized = false;
    bool folding_list = false;
    bool[] foldings;

    public override void OnInspectorGUI()
    {
        ListController ctrl = target as ListController;

        var list = ctrl.list;

        // --削除--
        //foldings = new bool[list.Count];

        // --追加--
        if (!isInitialized) 
        {
            foldings = new bool[list.Count];
            isInitialized = true;
        }
        // --ここまで--

        if (folding_list = EditorGUILayout.Foldout(folding_list, "List"))
        {
            EditorGUI.indentLevel++;

            for(int i = 0; i < list.Count; i++)
            {
                EditorGUI.indentLevel++;

                if (foldings[i] = EditorGUILayout.Foldout(foldings[i], "Status_" + i))
                {
                    list[i].name = EditorGUILayout.TextField("Name", list[i].name);
                    list[i].hp = EditorGUILayout.IntField("HP", list[i].hp);
                    list[i].mp = EditorGUILayout.IntField("MP", list[i].mp);
                }

                EditorGUI.indentLevel--;
            }

            if (GUILayout.Button("Add"))
            {
                list.Add(new Status("", 0, 0));

                // --追加--
                isInitialized = false;
                // --ここまで--
            }

            EditorGUI.indentLevel--;
        }
    }
}
```

![スクリーンショット 2018-01-23 12.39.31.png](https://qiita-image-store.s3.amazonaws.com/0/106605/c608a035-2f58-1322-e974-5219bf0b1830.png)

これで無事開けるようになりました！
Addすると上のようにちゃんと追加されます。

#改良版

```C#:ListControllerEditor.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(ListController))] 
public class ListControllerEditor : Editor 
{
    bool isInitialized = false;
    bool folding_list = false;
    bool[] foldings;

    public override void OnInspectorGUI()
    {
        ListController ctrl = target as ListController;

        var list = ctrl.list;

        // --追加--
        if (!isInitialized) InitializeList(list.Count);
        // --ここまで--

        if (folding_list = EditorGUILayout.Foldout(folding_list, "List"))
        {
            EditorGUI.indentLevel++;

            for(int i = 0; i < list.Count; i++)
            {
                EditorGUI.indentLevel++;

                // 表示名をStatusの要素に変更
                if (foldings[i] = EditorGUILayout.Foldout(foldings[i], list[i].name))
                {
                    list[i].name = EditorGUILayout.TextField("Name", list[i].name);
                    list[i].hp = EditorGUILayout.IntField("HP", list[i].hp);
                    list[i].mp = EditorGUILayout.IntField("MP", list[i].mp);

                    // --変更--
                    EditorGUILayout.BeginHorizontal();

                    // いっぱいまで空白を埋める
                    GUILayout.FlexibleSpace();

                    if (GUILayout.Button("Delete"))
                    {
                        list.RemoveAt(i);

                        InitializeList(i, list.Count);
                    }

                    EditorGUILayout.EndHorizontal();
                    // --ここまで--
                }

                EditorGUI.indentLevel--;
            }

            // --変更--
            if (GUILayout.Button("Add"))
            {
                list.Add(new Status("New Status", 0, 0));

                InitializeList(-1, list.Count);
            }
            // --ここまで--

            // インデントを減らす
            EditorGUI.indentLevel--;
        }
    }

    // Listの長さを初期化
    void InitializeList(int count)
    {
        foldings = new bool[count];
        isInitialized = true;
    }

    // 指定した番号以外をキャッシュして初期化 (i = -1の時は全てキャッシュして初期化)
    void InitializeList(int i, int count)
    {
        bool[] foldings_temp = foldings;
        foldings = new bool[count];

        for (int k = 0, j = 0; k < count; k++)
        {
            if (i == j) j++;
            if (foldings_temp.Length - 1 < j) break;
            foldings[k] = foldings_temp[j++];
        }
    }
}

```

![スクリーンショット 2018-01-23 14.02.15.png](https://qiita-image-store.s3.amazonaws.com/0/106605/3a182c0b-5395-88ac-74f4-afb7fbd17bef.png)

先程はAddとDeleteをする度に折りたたまれていたのですが、修正しました。

BeginHorizontal ~ EndHorizontalで囲う理由は
FlexibleSpaceを使うと画面下いっぱいにまでSpaceが作られてしまうためです。

#最後に

もし順番の入れ替えをしたいならReorderableListを使うといいと思います。
気が向いたらまとめようと思います。

もっといい方法や間違っている点などがあればコメントお願いします。