【Unity】【エディタ拡張】SerializedObjectの勘所をまとめてみる

SerializedObjectについて理解が浅い部分があったので、
ここまで知っておけばまあいいだろう、という程度でまとめておきます。

SerializedObjectとそのメリット

SerializedObjectは、オブジェクトのもつ値をUnityで扱いやすくしたものです。

エディタ上ではSerializedObjectを通して値を編集することにより、以下のようなメリットが得られます。

  • privateフィールドの値にもアクセスできるのでエディタ拡張の自由度が広がる
  • Undoのハンドリングを行ってくれる
  • Selectionのハンドリングを行ってくれる

カスタムエディタでプロパティを編集する

それではSerializedObjectの編集の仕方を見ていきます。
まずカスタムエディタからプロパティを編集してみます。

using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class SerializedObjectTest : MonoBehaviour
{
    [SerializeField]
    private int _testInt;
}

#if UNITY_EDITOR

[CustomEditor(typeof(SerializedObjectTest))]
public class SerializedObjectTestEditor : Editor
{
    public override void OnInspectorGUI()
    {
        // 内部キャッシュから値をロードする
        serializedObject.Update();
        
        // プロパティを取得する
        var testIntProperty = serializedObject.FindProperty("_testInt");
        
        // プロパティをインスペクタから編集できるように
        EditorGUILayout.PropertyField(testIntProperty);

        // 内部キャッシュに値を保存する
        serializedObject.ApplyModifiedProperties();
    }
}

#endif

プロパティを取得するにはSerializedObject.FindProperty()を使います。
その値をEditorGUILayout.PropertyField()の引数に渡すことでインスペクタ上から編集可能になります。

また、SerializedObjectにアクセスする前にはSerializedObject.Update()を呼んで値を最新の状態にします。

編集後はSerializedObject.ApplyModifiedProperties()を呼んで値を保存します。

カスタムエディタでオブジェクトの参照を得る

次に他のオブジェクトへの参照を持つプロパティから、参照先のSerializedObjectを取得します。

using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class SerializedObjectTest : MonoBehaviour
{
    [SerializeField]
    private Text _textComponent;
}

#if UNITY_EDITOR

[CustomEditor(typeof(SerializedObjectTest))]
public class SerializedObjectTestEditor : Editor
{

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        
        var textProperty = serializedObject.FindProperty("_textComponent");
        EditorGUILayout.PropertyField(textProperty);

        serializedObject.ApplyModifiedProperties();

        // SerializedProperty.objectReferenceValueでオブジェクトの参照を取得
        var textRef = textProperty.objectReferenceValue;
        if (textRef != null) {
            // テキストのSerializedObjectを得る
            var textSO = new SerializedObject(textRef);
            textSO.Update();

            // テキストのSerializedObjectを編集する
            var textSOProp = textSO.FindProperty("m_Text");
            EditorGUILayout.PropertyField(textSOProp, new GUIContent("Textコンポーネントのテキスト"));

            textSO.ApplyModifiedProperties();
        }
    }
}

#endif

説明はコメントの通りです。
SerializedProperty.objectReferenceValueで取得した参照をSerializedObjectのコンストラクタに渡すことでそのオブジェクトのSerializedObjectが得られます。

あとは前節と同じような方法で編集できます。

カスタムエディタでリストを操作する

Listの編集は以下のように実装します。

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class SerializedObjectTest : MonoBehaviour
{
    [SerializeField]
    private List<int> _list = new List<int>();
}

#if UNITY_EDITOR

[CustomEditor(typeof(SerializedObjectTest))]
public class SerializedObjectTestEditor : Editor
{

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

        serializedObject.Update();
        
        var listProperty = serializedObject.FindProperty("_list");

        using (new EditorGUILayout.HorizontalScope()) {

            // 要素を追加
            if (GUILayout.Button("Add")) {
                listProperty.InsertArrayElementAtIndex(listProperty.arraySize);
            }
            // 要素を削除
            if (GUILayout.Button("Remove")) {
                if (listProperty.arraySize >= 1) {
                    listProperty.DeleteArrayElementAtIndex(listProperty.arraySize - 1);
                }
            }
            // 要素をすべて削除
            if (GUILayout.Button("Clear")) {
                listProperty.ClearArray();
            }
        }

        serializedObject.ApplyModifiedProperties();
    }
}

#endif

中身を見る

さて、プロパティを取得するにはプロパティ名を知る必要があります。
自分で作ったクラスであればフィールド名を入力すればいいのですが、例えばUnityで用意されているコンポーネントの値を編集する場合はプロパティ名を知らないといけません。

SerializedObjectのプロパティ名は次のように調べられます。

var iterator = serializedObject.GetIterator ();
while (iterator.NextVisible(true)){
    Debug.Log (iterator.propertyPath);
}

以上です。