はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】はじめてでも簡単インスペクターのエディタ拡張の手順

はじめに

みなさんエディタ拡張していますか?

私はエディタ拡張という名前からかなり高度な知識が必要では?と初めは思っていたのですが、実際に触ってみると意外とシンプルだったと感じました。

この記事ではインスペクターのエディタ拡張を扱い、以下の画像のよう見た目・操作性などのメリットをもたらす事ができます。

f:id:hanaaaaaachiru:20201014171450p:plain
ScriptalbeObjectのインスペクター拡張

特にScriptableObjectというデータを扱うアセットのインスペクターをエディタ拡張するとデータ管理が圧倒的に楽になりオススメですね。

こういったインスペクター拡張の手順を紹介していこうと思います。

またここではUIElementsではなく昔ながらのIMGUIでやっていくのであしからず。

対象について

まずインスペクターのエディタ拡張をするにあたって対象のオブジェクトが必要になります。

というわけで以下の2つのクラスを用意しました。

public class MonoBehaviourModel : MonoBehaviour
{
    public int publicValue;
    [SerializeField] private int serializedValue;

    public void Log()
    {
        Debug.Log($"publicValue: {publicValue}");
        Debug.Log($"serializedValue: {serializedValue}");
    }
}
[CreateAssetMenu()]
public class ScriptableObjectModel : ScriptableObject
{
    public int publicValue;
    [SerializeField] private int serializedValue;

    public void Log()
    {
        Debug.Log($"publicValue: {publicValue}");
        Debug.Log($"serializedValue: {serializedValue}");
    }
}

MonoBehaviourを継承しているクラスはゲームオブジェクトにアタッチすることでインスタンス化され,ScriptableObjectを継承しているクラスはアセットを作成することでインスタンス化されます。

それぞれのインスペクターはこんな感じ。

f:id:hanaaaaaachiru:20201014173551p:plainf:id:hanaaaaaachiru:20201014173554p:plain
←MonoBehaviorのインスペクター  ScriptabelObjectのインスペクター->

手順

1. Editorフォルダを作成する

エディタ拡張関連のスクリプトはEditorという名前のフォルダの中にいれないと動作しません。

Editorフォルダはルートになければいけないという制約があるわけではないので、好きな場所に作ってみてください。

f:id:hanaaaaaachiru:20201014174319p:plain

次から書くスクリプトは全部この中に入れます。

2. Editorクラスを継承,CustomEditor属性をつける

早速先ほど作成したオブジェクトのインスペクター拡張を開始しましょう。

まずはEditorクラスを継承したクラスを作成します。
UnityEditor.Editor - Unity スクリプトリファレンス

このときにインスペクター拡張したい対象のクラスをCustomEditor属性で指定してあげてください。
カスタムエディター - Unity マニュアル

using UnityEditor;

[CustomEditor(typeof(MonoBehaviourModel))]
public class MonoBehaviourModelEditor : Editor
{
}

3. OnInspectorGUIを実装する

次にOnInspectorGUIメソッドを作成します。

これはEditorクラスにvirtualなメソッドとして定義されていて、Unityがインスペクター上でエディターを表示するたびに実行されます

public virtual void OnInspectorGUI ();

Editor-OnInspectorGUI - Unity スクリプトリファレンス

using UnityEditor;

[CustomEditor(typeof(MonoBehaviourModel))]
public class MonoBehaviourModelEditor : Editor
{
    public override void OnInspectorGUI()
    {

    }
}

4. 値を取得・表示する

SerializedObjectを使ったやり方

次に拡張の対象のクラスで定義されていたフィールドの値を取得・更新してみます。

実は対象オブジェクトとのやり取りは2種類の方法がありますが、最初はSerializedObjectを通したやり方の説明をします。

フィールドの取得にはSerializedObject.FindProperty
SerializedObject-FindProperty - Unity スクリプトリファレンス
EditorGUILayout-PropertyField - Unity スクリプトリファレンス

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(MonoBehaviourModel))]
public class MonoBehaviourModelEditor : Editor
{
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        // フィールドを取得する
        var publicValue = serializedObject.FindProperty("publicValue");
        var serializedValue = serializedObject.FindProperty("serializedValue");

        // インスペクターに表示する
        EditorGUILayout.PropertyField(publicValue);
        EditorGUILayout.PropertyField(serializedValue);

        serializedObject.ApplyModifiedProperties();
    }
}

またFindPropertyメソッドの返り値はSerializedPropertyという型なので,中身の値を取り出すのにはひと工夫が必要になります。

int value1 = publicValue.intValue;
int value2 = serializedValue.intValue;

int型の場合はintValueですが、型によって取り出し方が異なります。

詳細はSerializedPropertyのドキュメントをご覧ください。
UnityEditor.SerializedProperty - Unity スクリプトリファレンス

さらにこの手法を用いたときは以下の手順でSerializeObjectの更新が必要になります。

OnInspectorGUIメソッドの先頭にserializedObject.Update();,最後にserializedObject.ApplyModifiedProperties();を書きます。
SerializedObject-Update - Unity スクリプトリファレンス
SerializedObject-ApplyModifiedProperties - Unity スクリプトリファレンス

これをすることでUnityが内部でキャッシュしている値を取得,更新することができるので、ウィンドウを複数開いていじるなんてことも可能になります。

using UnityEditor;

[CustomEditor(typeof(MonoBehaviourModel))]
public class MonoBehaviourModelEditor : Editor
{
    public override void OnInspectorGUI()
    {
        serializedObject.Update();

        // プロパティの取得・表示など

        serializedObject.ApplyModifiedProperties();
    }
}
対象オブジェクトに直接使うやり方

次にSerializedObjectを使わないやり方を紹介します。

こちらの場合にはEditorクラスのプロパティからキャストしてあげることで,対象のインスタンスを取得することができます。

using UnityEditor;

[CustomEditor(typeof(MonoBehaviourModel))]
public class MonoBehaviourModelEditor : Editor
{
    private MonoBehaviourModel _target;

    private void OnEnable()
    {
        // targetがObject型なのでキャストが必要
        _target = (MonoBehaviourModel)target;
    }

    public override void OnInspectorGUI()
    {
        // 値を取得
        var publicValue = _target.publicValue;
        // SerializedObjectを使わないやり方だとprivateなフィールドにはアクセスできない
        // var serializedValue = _target.serializedValue;

        // インスペクターに表示
        publicValue = EditorGUILayout.IntField("Public Value", publicValue);
    }
}

この方法はstringで指定がないところが魅力的ですが、自分でデータを保存する処理を書かなければなりません。

// ScriptabelObjectを継承している場合
public override void OnInspectorGUI()
{
    EditorGUI.BeginChangeCheck();

    _target.publicValue = EditorGUILayout.IntField("Public Value", _target.publicValue);

    if (EditorGUI.EndChangeCheck())
    {
        Undo.RecordObject(_target, "Change Property");
        EditorUtility.SetDirty(_target);
    }
}

本来はシーン上にオブジェクトがあるときにUndo.RecordObject,シーン上にないときはEditorUtillity.SetDirtyを呼べば動作する?みたいですが、別に2つともかいておいても不具合が発生したことはないので私はどっちも書いてしまっています。
Undo-RecordObject - Unity スクリプトリファレンス
EditorUtility-SetDirty - Unity スクリプトリファレンス

5. 機能をもりもりにする

後はGUILayoutEditorGUIEditorGUILayoutあたりを使って見た目・機能をもりもりにしちゃいましょう。

こちらの記事でたくさん紹介されていたので,是非みてみてください。
hacchi-man.hatenablog.com

さいごに

これで基本的なことはOKだと思います。

あとは実際に作りながら色々と学んでみるのが良いのではないでしょうか。

こちらのサイトもすごいオススメです↓
light11.hatenadiary.com

良きエディタ拡張ライフを!