[Unity] インスペクタから編集可能なprivateフィールドにアクセスする

Unityではシリアライズ可能なフィールドは以下のようにインスペクタから編集することが出来ます。

private-editor-access1

MonoBehaviourを継承したクラスには[Serializable]属性が指定されているため、シリアライズすることが出来ます。
シリアライズ可能なクラスでは、publicまたは[SerializeField]属性が指定されたフィールドがインスペクタ上に表示され、編集可能になります。
すなわち、[SerializeField]属性が指定されていればprivateなフィールドでも編集可能になります。

では、エディタ拡張などで[SerializeField]属性が指定されたprivateフィールドにアクセスする場合はどうすればよいでしょうか?
例えば、以下のようなEnemyクラスを実装したとします。

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {
    // Inspectorから編集可能なprivateフィールド
    [SerializeField]
    private int speed;
    [SerializeField]
    private int life;
    [SerializeField]
    private int attack;
}

ここでエディタ拡張するために以下のようなEnemyEditorクラスを定義します。

using UnityEditor;

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

        var enemy = (Enemy)target;

        // 各フィールドはprivateなのでコンパイルエラー
        enemy.speed = EditorGUILayout.IntSlider(enemy.speed, 0, 50);
        enemy.life = EditorGUILayout.IntSlider(enemy.life, 0, 100);
        enemy.attack = EditorGUILayout.IntSlider(enemy.attack, 0, 25);

        serializedObject.ApplyModifiedProperties();
    }
}

しかし、Enemyクラスのフィールドは皆privateであるため、外側からアクセスできません。
したがって、上記のEnemyEditorクラスはコンパイルエラーとなります。
フィールドをpublicにすればアクセス可能になりますが、エディタ拡張のためにわざわざフィールドを公開してしまうのは好ましくありません。

このようにシリアライズされたフィールドにアクセスするためには、SerializedObjectを用います。
具体的な使い方は以下の通りです。

using UnityEditor;

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

        // serializedObjectからSerializedPropertyを取得
        var speed = serializedObject.FindProperty("speed");
        var life = serializedObject.FindProperty("life");
        var attack = serializedObject.FindProperty("attack");

        // SerializedProperty経由で値にアクセスするのでコンパイルエラーとならない
        speed.intValue = EditorGUILayout.IntSlider(speed.intValue, 0, 50);
        life.intValue = EditorGUILayout.IntSlider(life.intValue, 0, 100);
        attack.intValue = EditorGUILayout.IntSlider(attack.intValue, 0, 25);

        serializedObject.ApplyModifiedProperties();
    }
}

これでEnemyEditorクラスからEnemyクラスのフィールドにアクセスすることが出来ます。
インスペクタを表示してみると以下のようにEnemyEditorによってスライダーにより値が編集できるようになっていることを確認できます。

private-editor-access2

エディタ拡張でシリアライズ可能なフィールドにアクセスする場合は、基本的にSerializedObjectを通じてアクセスする習慣をつけておけば問題無いかと思います。