[Unity] カスタムエディタを使ってインスペクタをリッチにする


Unityは通常のスクリプト以外にも、インスペクタを拡張する便利な機能がたくさんあります。
そのうちのひとつに「カスタムエディタ」があります。

*エディタスクリプト全体については[こちらにとても貴重な資料がある](http://anchan828.github.io/editor-manual/web/customeditor.html)のでぜひ見てみてください。*

## 基本形

カスタムエディタの基本的はこんな感じになります。
（ちなみに余談ですが、エディタスクリプトは `Editor` というディレクトリに保存します（ディレクトリの位置は任意））

```csharp:AnyClassNameInspector.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(AnyClassName))]
public class AnyClassNameInspector : Editor
{
    //
}
```

上記のように `CustomEditor` Attributeを利用し、 `Editor` クラスを継承したクラスを作成します。
こうすることによって、 `typeof` で渡されたクラスがインスペクタに表示される際に、このカスタムエディタクラスが適用され、自由にカスタマイズすることが可能になります。

## インスペクタのGUIのカスタマイズ

インスペクタのGUIは `OnInspectorGUI` をオーバーライドすることでカスタマイズできます。
ごく簡単なサンプルは以下のようになります。

```csharp:AnyClassNameInspector.cs
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(AnyClassName))]
public class AnyClassNameInspector : Editor
{
    AnyClassName anyClassName = null;
    
    void OnEnable()
    {
        // AnyClassNameコンポーネントを取得
        anyClassName = target as anyClassName;
    }
    
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        
        EditorGUILayout.LabelField("hoge", anyClassName.anyProperty.ToString());
    }
}
```

`target` プロパティが定義されており、そのプロパティに、 `typeof` で指定したクラスの参照が渡されます。
これを適切にキャストした上で、該当クラスのインスタンス変数から適宜、GUIに反映させていく、というのがおおまかな流れになります。

## EditorGUIクラス

主に、インスペクタにGUIを表示するために利用するクラスです。
インスペクタを拡張する場合は必ず学習する必要があるクラスです。

UnityのUIを作るときに使用する `GUI` クラスのエディタ版、と考えると分かりやすいと思います。

## EditorGUILayoutクラス

[ドキュメント](http://docs.unity3d.com/ja/current/ScriptReference/EditorGUILayout.html)によると

>EditorGUIの自動レイアウトバージョン

とのこと。

### よく使いそうなもの

インスペクタのGUIを作る上で、よく使いそう or あとで思い出したくなりそうなものをざっくりメモ。

- EditorGUILayout.ObjectField
オブジェクトの参照を扱うフィールド。ドラッグ&ドロップで登録するいつものアレです。

![ObjectFieldサンプル](https://qiita-image-store.s3.amazonaws.com/0/5616/63efdd9c-35a3-ae81-6545-c6a855ed0484.png)
↑こんな感じの。

```csharp
EditorGUILayout.ObjectField(null, typeof(Object), false);
EditorGUILayout.ObjectField(null, typeof(GameObject), false);

// レイアウト情報付き
var options = new []{ GUILayout.Width(64), GUILayout.Height(64) };
EditorGUILayout.ObjectField(null, typeof(Texture), false, options);
```

- EditorGUILayout.LabelField
いわゆる「ラベル」。

上のと組み合わせるとこんな感じ。
![ラベルサンプル](https://qiita-image-store.s3.amazonaws.com/0/5616/7bc72d6a-7cf7-f611-9df6-f07e68d8676f.png)

- EditorGUILayout.HorizontalScope
横方向のグルーピング。横に短い要素など、横方向に並べたほうがいいGUIを配置する際に利用する。使い方は以下の感じ。

```csharp
using (new EditorGUILayout.HorizontalScope()) {
    EditorGUILayout.LabelField("any label");
    EditorGUILayout.LabelField("some label");
}
```

以上のような感じにすると `using` を利用した範囲において列挙したGUI要素が横方向にレイアウトされるようになります。

上のサンプルに適用するとこんな感じ。
![レイアウトサンプル](https://qiita-image-store.s3.amazonaws.com/0/5616/9003243c-6e49-777d-e050-d6275e9cda8e.png)

- EditorGUILayout.Popup
いわゆるポップアップ型のUIを配置する。
利用イメージはこんな感じ↓

```csharp
public override void OnInspectorGUI()
{
    int index = 0;
    string[] list = new string[]{ "hoge", "fuga", "foo", "bar" };
    // `selectedIndex`には選択された新しいindexが格納される
    int selectedIndex = EditorGUILayout.Popup(index, list);
}
```


### 選択されたenumの値によって設定できるプロパティの表示を変更する

ありそうなケースとして、選択された `enum` の値に応じて、設定可能なプロパティを変更したい、ということがあると思います。
そんなときに使える設定方法（のメモ）。

```csharp
public enum AnyType
{
    TypeA,
    TypeB,
    TypeC,
    TypeD,
}

public class AnyClass : MonoBehaviour
{
    [SerializeField]
    public AnyType AnyType;

    [SerializeField][HideInInspector]
    private string _anyName = "";

    [SerializeField][HideInInspector]
    private bool _anyBool = false;

    [SerializeField][HideInInspector]
    private Vector3 _anyVector = Vector3.zero;

    [SerializeField][HideInInspector]
    private float _anyFloat = 0f;

    // ... actual define.
}

#if UNITY_EDITOR
[CustomEditor(typeof(AnyClass))]
public class AnyClassInspector : Editor
{
    private AnyClass _target;
    private SerializedProperty _anyNameProperty;
    private SerializedProperty _anyBoolProperty;
    private SerializedProperty _anyFloatProperty;
    private SerializedProperty _anyVectorProperty;

    private void OnEnable()
    {
        _target = target as AnyClass;

        // 各種Propertyを取得する
        _anyNameProperty = serializedObject.FindProperty("_anyName");
        _anyBoolProperty = serializedObject.FindProperty("_anyBool");
        _anyFloatProperty = serializedObject.FindProperty("_anyFloat");
        _anyVectorProperty = serializedObject.FindProperty("_anyVector");
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        // シリアライズされたPropertyを更新しておく
        serializedObject.Update();

        switch (_target.AnyType)
        {
            case AnyType.TypeA:
                EditorGUILayout.PropertyField(_anyNameProperty);
                break;
            case AnyType.TypeB:
                EditorGUILayout.PropertyField(_anyNameProperty);
                EditorGUILayout.PropertyField(_anyBoolProperty);
                break;
            case AnyType.TypeC:
                EditorGUILayout.PropertyField(_anyNameProperty);
                EditorGUILayout.PropertyField(_anyFloatProperty);
                break;
            case AnyType.TypeD:
                EditorGUILayout.PropertyField(_anyNameProperty);
                _anyVectorProperty.vector3Value = EditorGUILayout.Vector3Field(_anyVectorProperty.displayName, _anyVectorProperty.vector3Value);
                break;
        }

        // 更新された値を適用する
        serializedObject.ApplyModifiedProperties();
    }
}
#end
```

## 覚えておくと便利そうなやつ

- EditorGUIUtility
1行の高さ、とか現在のビューのサイズ、とかの「それ知りたい」っていうのを提供してくれるクラス。

- EditorGUI.indentLevel
EditorGUIのインデントのレベル。
整数なので `EditorGUI.indentLevel++;` とかすると、その次のレイアウトからインデントがひとつ増え、逆に `--` するとインデントレベルがひとつ下がる、という感じで手軽に使える。