Unityのエディタ拡張でシリアライズ対象の値を直接編集する際の挙動についてまとめました。
Unity 2020.1.17f1
基本はSerializedObjectを使って更新する
いま、シリアライズ対象の値を持った MonoBehaviour
や ScriptableObject
について考えます。
以下は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
はオブジェクトに加わった変更の差分のみを記録する違いがあるようです。
なお、これらのメソッドはオブジェクトに加わった変更の全てを記録できるわけではなく、
オブジェクトの生成や破棄などについては他のメソッドを使う必要があります。
これについてはドキュメントを参照してください。
保存まで行う
ここまでの実装ではDirtyフラグを立てているだけなので、アセットの保存はされません。
もしアセットの保存までを行いたい場合には AssetDatabase.SaveAssets
を呼びます。
AssetDatabase.SaveAssets();
Play Modeの挙動
Play Mode中にこれらの処理を実行した際には、シーンに配置されているMonoBehaviourについては再生終了時にシリアライズされている値が元に戻ります。
PrefabやScriptableObjectなど独立したアセットに関してはPlay Mode中に変更した値が反映されます。
この辺りはInspectorから値を変更した時の挙動と同じです。