概要
OnInspectorGUI内でコンポーネントの値を書き換えるとエディターが変更を認知してくれず、シーンを読み直したり実行ボタンを押したりすると変更が消えてしまう。
解決策
-
SerializedObject.Update、EditorGUILayout.PropertyField、SerializedObject.ApplyModifiedPropertiesを使う。
-
Undo.RecordObjectとEditorUtility.SetDirtyを使う。(この記事での方法)
詳細
Unityを5.3.xにアップデートしたらInspectorの変更が保存されなくなってしまった…。 (´・ω・`)
こちらの記事で言及されているのとほぼ同じ問題です。
本来は上記記事で言及されているようにSerializedObjectとSerializedProperty経由で変更し、ApplyModifiedPropertiesするようにすれば解決できます。
ただ私の場合、OnInspectorGUIでEditorGUILayout.IntFieldなどを使用してプロパティー一個一個を処理しているわけではなく、
このようにインスペクタ内に自動処理・一括処理のためのボタンを表示したいためにOnInspectorGUIを使用しており、またこの一括・自動処理で変更されるプロパティーがSystem.Serializable属性を付けた自作のstruct/classの配列(しかも入れ子になっている)であるためSerializedPropertyを使った値の変更が大変面倒で現実的ではありませんでした。
Undo.RecordObjectでは配列のサイズのみが変更点と認識され、
実行ボタンを押すと配列要素に設定された内容が消えてしまいます。
OnInspectorGUI内でのUndo.RecordObjectはこのような挙動をするようです。
PrefabをApplyしてしまえばこのボタンでの変更結果はちゃんと保存されるのですが、そうする前にテストプレイして具合を確かめたりしたいわけなので、やはり実行ボタンを押すと消えてしまうのは具合が悪いです。
あと、おそらくOnInspectorGUIの中でこの処理をせず、メニューから呼び出すタイプのエディタ拡張にしてしまえばUndo.RecordObjectが普通に機能して何の問題も無いと思うのですが、やっぱりメニューアイテムにするよりインスペクターにボタンがある方が便利でわかりやすいです。
そこで試しに、Undo.RecordObjectしつつEditorUtility.SetDirtyを呼びようにすると…
ちゃんと変更点が認識され、実行ボタンを押しても消えてしまわず…
アンドゥもできるようになります。(配列のフィールド名がBoldのままですが)
[CanEditMultipleObjects]
[CustomEditor(typeof(Fighter))]
public class FighterInspector : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if(GUILayout.Button("Update Move Infos")) {
Undo.RecordObject(target, "Update Move Infos");
var fighter = target as Fighter;
fighter.UpdateMoveInfos();
EditorUtility.SetDirty(fighter);
}
if(GUILayout.Button("Compile Move Commands")) {
Undo.RecordObject(target, "Compile Move Commands");
var fighter = target as Fighter;
fighter.CompileMoveCommands();
EditorUtility.SetDirty(fighter);
}
}
}
OnInspectorGUIが呼び出されるたびにUndo.RecordObjectとEditorUtility.SetDirtyが呼ばれるようになっていたりすると何か具合の悪いことが起こりそうですが、今回の私の場合のようにボタンを押したときだけ呼び出すで十分な場合は、うまくいくようです。(少なくともUnity 2017.2では)
#というか
こういうインスペクターでなくとも実現可能なものはOnInspectorGUI内のGUILayout.Buttonなんか使わずにMenuItemやContextMenuを使え、ってことなんでしょうねぇ。MenuItem・ContextMenu・ContextMenuItemであれば、SetDirtyせずともRecordObjectだけでちゃんと動きます。
【追記】なぜか勘違いしていて、こういうユーザー定義型の配列の場合はどんな場合もRecordObjectだけではだめでSetDirtyが必要なようです。
手抜きしてインスペクター拡張
インスペクター内でのUndo.RecordObjectしてかつEditorUtility.SetDirtyというのはUnity4の時代から存在するイディオムだったみたいです。