I am encountering a strange behaviour with the undo action and custom editors in Unity 5.5. I created a custom editor for a script and everything is working properly except that undoing changes in the inspector does not cause the code inside the EndChangeCheck() block to be executed. Here’s an example of the code:
// UndoTest.cs
public class UndoTest : MonoBehaviour
{
public int myInt;
}
// UndoTestEditor.cs
[CustomEditor(typeof(UndoTest))]
public class UndoTestEditor : Editor
{
SerializedProperty myIntProp;
void OnEnable()
{
myIntProp = serializedObject.FindProperty("myInt");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(myIntProp);
if(EditorGUI.EndChangeCheck())
{
Debug.Log("Value changed to " + myIntProp.intValue);
}
serializedObject.ApplyModifiedProperties();
}
}
If I make changes to myInt in the inspector, the debug message is printed as expected. However, if I undo changes (ctrl+z), the myInt field reverts to its previous state, but the debug message is not printed, indicating that the change check was not triggered.
You might have to SetDirty() for your object, though I’ve read that when you work with serialized objects all of that is supposed to be handled for you.
However, I’m not entirely convinced that an undo would cause a change check to occur in the first place. You’re restoring data outside of the OnGUI () function, so the code to measure your changes might not intersect with the undo command.
This is all off the top of my head though, so it warrants some research
Yeah, using SerializedProperty handles undo and redo automatically, but I think you’re right. It seems like EndChangeCheck() only returns true if I physically make changes to the control in the inspector, but not if a change is made to the value that the serialize property links to. Sigh, that’s disappointing.
I tried changing the value of myInt from another script. The value was reflected in the inspector, but the change wasn’t detected in the editor script.
You could store the value of the int in another variable at the end of the change check. Then during another event type (maybe repaint?) Check to see if the serialized value matches your cached value.
I thought about doing that but the issues I have with that method are:
The actual script that I’m writing the editor for has multiple variables that need to be checked
Editor objects are destroyed when the target object is deselected, so the cached values will be lost when the target is re-selected. This means I would have to store them in the first script, and using up extra memory.
I came up with a way that kinda works. It uses getters/setters and OnValidate(). The only issue I have with it is that the entire body of OnValidate() is executed when anything changes in the inspector. See below:
// UndoTest.cs
public class UndoTest : MonoBehaviour
{
[SerializeField]
private int _myInt1;
public int myInt1
{
get { return _myInt1; }
set
{
// Do what needs to be done when a change occurs.
_myInt1 = Mathf.Clamp(value, 0, 10);
print("myInt1 changed to " + _myInt1);
}
}
[SerializeField]
private int _myInt2;
public int myInt2
{
get { return _myInt2; }
set
{
// Do what needs to be done when a change occurs.
_myInt2 = Mathf.Clamp(value, -10, 10);
print("myInt2 changed to " + _myInt2);
}
}
void OnValidate()
{
myInt1 = _myInt1;
myInt2 = _myInt2;
}
}
// UndoTestEditor.cs
[CustomEditor(typeof(UndoTest))]
public class UndoTestEditor : Editor
{
SerializedProperty myInt1Prop;
SerializedProperty myInt2Prop;
void OnEnable()
{
myInt1Prop = serializedObject.FindProperty("_myInt1");
myInt2Prop = serializedObject.FindProperty("_myInt2");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(myInt1Prop);
EditorGUILayout.PropertyField(myInt2Prop);
serializedObject.ApplyModifiedProperties();
}
}
If anybody has a better way to do this, please share.
I don’t see how that will help. If I understand it correctly, using that means specifying a function that is called whenever an undo or redo occurs. If I use that, I still need to know exactly which variable changed. Also, something that I didn’t realize earlier, is that my script also needs to track changes made to its variables by other scripts. So using setters and getters is probably the best way to go, since Begin/EndChangeCheck() doesn’t work the way I thought it would.
In my case, I needed to react to any property change and undo of a group of MonoBehaviours (with a common base class) to refresh an Editor Window, and didn’t need to know what property changed exactly, so Undo.undoRedoPerformed did the job.
However, it’s far from being optimal, as it still detects any undo of any kind really, while I only care about that certain group of MonoBehaviours. Fortunately, in this case the Editor Window is only showing stuff when an object with said MonoBehaviour is selected.
I understand why Undo would not trigger custom editor change check, but it makes it very hard to work with custom view synchronized with component properties.
Fortunately, 90% of my custom views are done via Handles, which are magically updated to follow both manual changes and changes via undo. I think it’s because the Handles code is done on the editor directly associated to the component whose properties are changed. When working on an external editor window, all bets are off.
It would be great to associate an editor window to a group of components it observes and refresh it on any property change. Instead of Undo.undoRedoPerformed, we could have an event associated to the component like MonoBehaviour.onPropertyChanged. I guess that one issue is that it’s more an Editor-specific event, so it could be put on the Editor class instead, except that the Undo action itself is unrelated to the presence of a visible Editor, since you could even Undo properties blindly without Inspector.