Supporting Undo in Unity Editor scripts
date
Aug 23, 2023
type
KnowledgeBase
year
slug
serializedobject
status
Published
tags
Unity
Undo
Serialization
summary
There’s 2 ways to do this: Via Serialization or via the
Undo
class. Let’s have a look at both!There's 2 ways to do this: Via Serialization or via the
Undo
class.- The 🏆 recommended way is to work with Serialization, then Undo is more or less automatically handled for you whenever you apply changes to a
SerializedObject
. In addition this ensures that serialized fields are automatically dirtied and styled correctly for Prefab overrides.
- The other option is to use the
Undo
class and create Undo-steps manually before you change an Object. For newly created objects you create a creation-undo-step after you created the new object. - Only use this in emergencies.
Undo via Serialization
Undo will be handled almost automatically for you if you work with
SerializedObject
s and SerializedProperty
s. What’s a
SerializedObject
? A class for editing serialized fields on Unity objects in a completely generic way.Usually all you have to do is this:
- ⬇️ Call
Update()
on theSerializedObject
to make sure it’s got all the latest values from the actual object
- 🔄 Edit the
SerializedProperty
s of theSerializedObject
- ⬆️ Call
ApplyModifiedProperties()
on theSerializedObject
, so that the changes get written back to the actual object.
Looks like this in code:
serializedObject.Update(); // Update SerializedObject // Do stuff with the serialized object EditorGUILayout.PropertyField(serializedObject.FindProperty("m_MyField")); // Maybe disaplay a PropertyField for the user to edit serializedObject.FindProperty("m_Value").floatValue = 666f; // Or change something directly serializedObject.ApplyModifiedProperties(); // Apply modifications
Working with SerializedObject
and SerializedProperty
Getting a SerializeObject
If you make a custom Inspector for an object you will automatically get a
serializedObject
to work with.[CanEditMultipleObjects] [CustomEditor(typeof( Spaceship ), true)] public class SpaceshipEditor : Editor { public override void OnInspectorGUI() { serializedObject.Update(); // <--- Ready to use } }
Otherwise you can create a SerializedObject for any Object like this:
SerializedObject so = new SerializedObject( transform );
Getting SerializedProperty
s from a SerializedObject
To change values on a
SerializedObject
we need to get them as SerializedProperty
s. - This is a generic way to deal with serialized objects, remember. - So what we have to do is call FindProperty
:SerializedProperty sProp = serializedObject.FindProperty( "m_Value" ); SerializedProperty sPropStr = serializedObject.FindProperty( "m_TargetName" );
As you see we need to know the field name for this. You can
Shift
+RMB
any field in the inspector and click on “Print Property Path” to log that field’s name into the console.Or you can switch the inspector to Debug mode and hold the
Alt
key for a while - then all field names will show up.To change the value of a
SerializedProperty
you kinda need to know it’s type. Every SerializedProperty
has fields for stringValue
, boolValue
, floatValue
, intValue
, rectValue
, vector3Value
, animationCurveValue
, etc., etc.If you don’t know the type for some reason you can look it up via
sProp.type
(a string) or sProp.propertyType
(to compare against the SerializedPropertyType enum)Further Reading: 🌐 → SerializedProperty Docs
And one more cool thing: You can change multiple things at once! If you turn an Array into a
SerializedObject
and change one of its properties it will update all the objects. Even for different kinds of objects (at least as long as they all have that same field that you’re changing…)Here’s the example from the Unity Docs:
var transforms = Selection.gameObjects.Select( go => go.transform ).ToArray(); var so = new SerializedObject( transforms ); so.FindProperty( "m_LocalPosition" ).vector3Value = Vector3.zero; so.ApplyModifiedProperties();
Undo via Undo
Class
Lets you register undo operations on specific objects before you change them.
Change some properties:
Undo.RecordObject( transform, "Change some stuff" ); transform.position = Vector3.zero; transform.eulerAngles = new Vector3( 0f, 90f, 0f );
Add a component:
Undo.AddComponent<Rigidbody>( gameObject );
Reparent Transform:
Undo.SetTransformParent( transform1, parentToTransform, "Set new parent" );
Create GameObject:
var go = new GameObject( "Buster" ); Undo.RegisterCreatedObjectUndo( go, "Create GameObject" );
Destroy GameObject (or component):
Undo.DestroyObjectImmediate( target );
You can also use this class to Clear the Undo and Redo stacks or to perform Undo and Redo operations via script.
Undo.PerformUndo(); Undo.PerformRedo(); Undo.ClearUndo(); Undo.ClearAll(); // Clear Undo and Redo
There’s also stuff in there for grouping undo operations together under a single name, etc. 🌐 → Undo Docs
Random Sidenote - I’ve had some fun in the past trying to get it to register changes to a
ScriptableObject
. I had to Undo + SetDirty + AssetDatabase.SaveAssets before it would actually show up as changed…Undo.RegisterCompleteObjectUndo( Style, "Adjust Style" ); EditorGUI.BeginChangeCheck(); Style.Style.border.left=EditorGUILayout.IntField("Left",Style.Style.border.left); Style.Style.border.right=EditorGUILayout.IntField("Right",Style.Style.border.right); Style.Style.border.top=EditorGUILayout.IntField("Top",Style.Style.border.top); Style.Style.border.bottom=EditorGUILayout.IntField("Bottom",Style.Style.border.bottom); if(EditorGUI.EndChangeCheck()){ EditorUtility.SetDirty(Style); AssetDatabase.SaveAssets(); }