【Unity】【エディタ拡張】シリアライズ対象の値を直接編集する際の挙動をちゃんと理解する

Unityのエディタ拡張でシリアライズ対象の値を直接編集する際の挙動についてまとめました。

Unity 2020.1.17f1

基本はSerializedObjectを使って更新する

いま、シリアライズ対象の値を持った MonoBehaviourScriptableObject について考えます。
以下はint型のシリアライズ対象の値を持った ScriptableObject の例です。

using UnityEngine;

[CreateAssetMenu]
public sealed class ExampleScriptableObject : ScriptableObject
{
    public int test;
}

このようなシリアライズ対象の値をエディタからスクリプトを通して編集する際には、基本的には以下のように SerializedObject を使います。

var serializedObject = new SerializedObject(scriptableObject);
serializedObject.Update();
serializedObject.FindProperty("test").intValue++;
serializedObject.ApplyModifiedProperties();

直接更新した時の挙動と問題

次にこの値を SerializedObjectを使わずに編集することを考えます。
以下ではスクリプトから直接変数を書き換えています。

ExampleScriptableObject scriptableObject;
scriptableObject.test++;

このように直接書き換えてしまうと、その変更はシリアライズされません。
つまり変更が保存されず、一度Unityを閉じてから開くと加えたはずの変更が元に戻ってしまいます。

Dirtyフラグを立てる

変更が保存されない理由は、その値をシリアライズしているアセットにDirtyフラグが立っておらず、保存対象となっていないためです。
以下のようにしてDirtyフラグを立てることで、次回 Ctrl + Sなどで保存する際に正常に保存されます。

ExampleScriptableObject scriptableObject;
scriptableObject.test++;

// Dirtyフラグを立てる
EditorUtility.SetDirty(scriptableObject);

Undoに対応する

さてこれで変更が保存されるようになりましたが、このままではUndoに対応していません。
Undoに対応するには、 Undo.RegisterCompleteObjectUndoを使用します。

ExampleScriptableObject scriptableObject;

// Undoに対応する
Undo.RegisterCompleteObjectUndo(scriptableObject, "test");

scriptableObject.test++;
EditorUtility.SetDirty(scriptableObject);

すると Edit > Undoメニューから変更をUndo/Redoできるようになります。

Undo.RegisterCompleteObjectUndoの代わりに Undo.RecordObjectを使うこともできます。 Undo.RegisterCompleteObjectUndo はオブジェクトの状態全てをそのまま記録しておくのに対し、
Undo.RecordObject はオブジェクトに加わった変更の差分のみを記録する違いがあるようです。

なお、これらのメソッドはオブジェクトに加わった変更の全てを記録できるわけではなく、
オブジェクトの生成や破棄などについては他のメソッドを使う必要があります。
これについてはドキュメントを参照してください。

docs.unity3d.com

保存まで行う

ここまでの実装ではDirtyフラグを立てているだけなので、アセットの保存はされません。 もしアセットの保存までを行いたい場合には AssetDatabase.SaveAssets を呼びます。

AssetDatabase.SaveAssets();

Play Modeの挙動

Play Mode中にこれらの処理を実行した際には、シーンに配置されているMonoBehaviourについては再生終了時にシリアライズされている値が元に戻ります。
PrefabやScriptableObjectなど独立したアセットに関してはPlay Mode中に変更した値が反映されます。

この辺りはInspectorから値を変更した時の挙動と同じです。

参考

docs.unity3d.com

docs.unity3d.com