• Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
3
Question by PicklesIIDX · Mar 11, 2015 at 10:24 PM · editorundoeditor window

What are the limitations of Undo.RecordObject?

So I'm having trouble understanding exactly how Undo works in custom editor windows. Here is the progress I've made:

I've discovered that although I can register undo events for objects that change, the undo command will not always revert those changes. For example, any serialized field of a member that belongs to an instance of a class does not seem to revert.

EDIT: The first post was pseudocode off of the top of my head. I've rebuilt this to provide as a compilable example:

 using System;
 using UnityEngine;
 using UnityEditor;
 
 public class FooWindow : EditorWindow {
 
     [SerializeField]
     Foo fooClass;
 
     [MenuItem("Window/Test Editor")]
     static void ShowEditor(){
         FooWindow editor = EditorWindow.GetWindow<FooWindow>();
         editor.title = "Test Editor";
         editor.Init();
     }
 
     void Init(){
         fooClass = CreateInstance<Foo>();
     }
 
     void OnGUI(){
         EditorGUI.BeginChangeCheck();
         bool toggle = GUILayout.Toggle(fooClass.SClass.Bar, "Bar");
         if(EditorGUI.EndChangeCheck()){
             Debug.LogWarning("Saved");
             Undo.RecordObject(fooClass, "Set bar");
             fooClass.SClass.Bar = toggle;
             EditorUtility.SetDirty(fooClass);
         }
     }
     
 }
 
 using UnityEngine;
 using UnityEditor;
 using System.Collections;
 
 public class Foo : Editor {
 
     [SerializeField]
     SerializedClass sClass;
     public SerializedClass SClass {
         get { return sClass; }
         set { sClass = value; }
     }
 
     public Foo(){
         sClass = new SerializedClass();
     }
 }
 
 
 using UnityEngine;
 using System.Collections;
 
 [System.Serializable]
 public class SerializedClass {
     
     [SerializeField]
     bool bar = false;
     public bool Bar {
         get { return bar; }
         set { bar = value; }
     }
 }
 

This will record the undo action, but will not revert it. Instead, If I move the undo code into the Foo class and then create another serialized variable within the Foo class and change that value, which I then would force the SerializedClass's Bar variable to, it will correctly undo the change. EDIT: I put together a small example here and am able to get both a boolean and an int array tracked through undo.

 using System;
 using UnityEngine;
 using UnityEditor;
 
 public class FooWindow : EditorWindow {
 
     [SerializeField]
     Foo fooClass;
 
     [MenuItem("Window/Test Editor")]
     static void ShowEditor(){
         FooWindow editor = EditorWindow.GetWindow<FooWindow>();
         editor.title = "Test Editor";
         editor.Init();
     }
 
     void Init(){
         GameObject go = new GameObject();
         SerializedClass sClass = go.AddComponent<SerializedClass>();
         fooClass = ScriptableObject.CreateInstance<Foo>();
         fooClass.Init(sClass);
     }
 
     void OnGUI(){
         fooClass.UpdateValues();
     }
     
 }
 
 using UnityEngine;
 using UnityEditor;
 using System.Collections;
 
 public class Foo : Editor {
     
     SerializedClass sClass;
     public SerializedClass SClass {
         get { return sClass; }
         set { sClass = value; }
     }
 
     public void Init(SerializedClass newSClass){
         sClass = newSClass;
     }
 
     public void UpdateValues(){
         EditorGUI.BeginChangeCheck();
         bool toggle = GUILayout.Toggle(sClass.Bar, "Bar");
         if(EditorGUI.EndChangeCheck()){
             Debug.LogWarning("Saved");
             Undo.RecordObject(sClass, "Set bar");
             sClass.Bar = toggle;
             EditorUtility.SetDirty(sClass);
         }
 
         if(GUILayout.Button("Array")){
             Debug.LogWarning("adding...");
             Undo.RecordObject(sClass, "Add Int");
             int[] intArray = sClass.IntArray;
             System.Array.Resize<int>(ref intArray, sClass.IntArray.Length + 1);
             sClass.IntArray = intArray;
             EditorUtility.SetDirty(sClass);
         }
 
     }
 }
 
 
 using UnityEngine;
 using UnityEditor;
 using System.Collections;
 
 public class SerializedClass : MonoBehaviour {
     
     [SerializeField]
     bool bar = false;
     public bool Bar {
         get { return bar; }
         set { bar = value; }
     }
 
     [SerializeField]
     int[] intArray = new int[0];
     public int[] IntArray{
         get { return intArray; }
         set { intArray = value; }
     }
 }
 

So, there is something about the Undo class that I don't understand. Why doesn't it restore properties of serialized values within a class? EDIT: in setting up this example I was able to get the int array working. But a similar setup doesn't work in my project. So there is something about what will actually be recorded and what won't that I'm not understanding. So my question is: how exactly does Undo.RecordObject() determine whether it will record and restore a change with the undo command?

Comment
Add comment · Show 5
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Baste · Mar 11, 2015 at 11:04 PM 0
Share

What version of Unity are you using?

I tested with both 4.6 and 5, and the method Undo.RegisterObject does not exist in any of them. It's not in the API. Undo.RecordObject is a thing, though.

Are those typos, or are you using a seriously old version of Unity? I'm suspecting typos, as your code isn't even close to compiling ("Class" ins$$anonymous$$d of "class", properties lacking types, etc.), but I want to make sure.

avatar image PicklesIIDX · Mar 12, 2015 at 01:24 AM 0
Share

So I wrote pseudocode off the top of my head. I totally had all of the typos in there. I have updated the question to use actual code. I'm using Unity 4.3. Doing so because of console publishing.

Although I was able to get the int array recording with my example, I still don't know exactly what's going on with this. Why does RecordObject require me to be within one object? Why am I able to create scenarios where it will record an undo step, but not restore the changes on Undo? I want to know exactly how this function works.

avatar image PicklesIIDX · Mar 12, 2015 at 03:59 AM 0
Share

I've been tweaking things and I've made some progress. It seems that a large part of my issue is that I'm editing prefabs, and not instances in the scene. These seem to provide different results. In my case, I want to edit prefabs. $$anonymous$$aybe there is a command that saves prefab changes to the Undo stack?

avatar image Baste · Mar 12, 2015 at 08:40 AM 0
Share

You're probably right in that this is a prefab issue. It might also be a bug.

I'll see if I can look into it - we might run into this issue later ourselves. Do you have some working example code?

avatar image PicklesIIDX · Mar 12, 2015 at 03:21 PM 0
Share

Here's an example to reproduce the error. Create these three classes, with Foo and TestEditor in your Editor folder and SerializedClass outside.

 using System;
 using UnityEngine;
 using UnityEditor;
 
 public class FooWindow : EditorWindow {
     
     Foo fooClass;
 
     [$$anonymous$$enuItem("Window/Test Editor")]
     static void ShowEditor(){
         FooWindow editor = EditorWindow.GetWindow<FooWindow>();
         editor.title = "Test Editor";
     }
 
     void OnSelectionChange(){
         if(Selection.activeGameObject.GetComponent<SerializedClass>() != null){
             SerializedClass sClass = Selection.activeGameObject.GetComponent<SerializedClass>();
             fooClass = ScriptableObject.CreateInstance<Foo>();
             fooClass.Init(sClass);
         } else {
             fooClass = null;
         }
     }
 
     void OnGUI(){
         if(fooClass != null){
             fooClass.UpdateValues();
         }
     }
     
 }


 using UnityEngine;
 using UnityEditor;
 using System.Collections;
 
 public class Foo : Editor {
     
     SerializedClass sClass;
     public SerializedClass SClass {
         get { return sClass; }
         set { sClass = value; }
     }
 
     public void Init(SerializedClass newSClass){
         sClass = newSClass;
     }
 
     public void UpdateValues(){
         EditorGUI.BeginChangeCheck();
         bool toggle = GUILayout.Toggle(sClass.Bar, "Bar");
         if(EditorGUI.EndChangeCheck()){
             Debug.LogWarning("Saved");
             Undo.RecordObject(sClass, "Set bar");
             sClass.Bar = toggle;
             // TODO: Find out how to save the prefab
             EditorUtility.SetDirty(sClass);
         }
 
         if(GUILayout.Button("Array")){
             Debug.LogWarning("adding...");
             Undo.RecordObject(sClass, "Add Int");
             int[] intArray = sClass.IntArray;
             System.Array.Resize<int>(ref intArray, sClass.IntArray.Length + 1);
             sClass.IntArray = intArray;
             EditorUtility.SetDirty(sClass);
         }
 
     }
 }
 

using UnityEngine; using UnityEditor; using System.Collections; public class SerializedClass : $$anonymous$$onoBehaviour {

 [SerializeField]
 bool bar = false;
 public bool Bar {
     get { return bar; }
     set { bar = value; }
 }

 [SerializeField]
 int[] intArray = new int[0];
 public int[] IntArray{
     get { return intArray; }
     set { intArray = value; }
 }

}

When this is done create a new gameobject in the scene. Then copy this to your project as a prefab. Open up the Test Editor from the Window menu. If you set Bar to true on the instance in the scene, it will undo properly. If you select the prefab, it will not undo. I'm going to keep looking into saving prefabs. Alternatively, a workaround might be to instantiate the objects while working on this, but that's not great for my program's intended flow.

4 Replies

· Add your reply
  • Sort: 
avatar image
3

Answer by SquarePieStudios · Jul 15, 2015 at 09:38 AM

I found that if I used RecordObject on the gameObject instead of the component instance, it worked just fine. Dumb...but I guess it works :/

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
3

Answer by oferei · Nov 02, 2016 at 01:13 PM

I just found out that Undo.RecordObject doesn't work on ScriptableObject, whereas EditorUtility.SetDirty does.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Vapid-Linus · Mar 03, 2018 at 09:28 AM 0
Share

I am also having problems getting Undo.RecordObject to work on a ScriptableObject. Problem is that EditorUtility.SetDirty doesn't record undo states.

avatar image
2

Answer by IgorAherne · Jul 19, 2017 at 10:13 PM

I found this post by complete accident:

quoting it in case it's deleted:

Undo.RegisterCompleteObjectUndo(Object objectToUndo, string name) will record a copy of the full state of the object that it will keep, unlike Undo.RecordObject(Object objectToUndo, string name) that will only keep a copy of the state until the end of the frame to compute a diff.

.

By the way, I just got the following error that drove me to find more on this method:

Generating diff of this object for undo because the type tree changed. This happens if you have used Undo.RecordObject when changing the script property. Please use Undo.RegisterCompleteObjectUndo So RegisterCompleteObjectUndo seems important in complex situations were a diff is not possible.

Couple of words from myself:

Undo.RegisterCompleteObjectUndo seems to work with Event and editor-GUI as well every time. And RecordObject seemed only to work sometimes (only worked occasionally)

Additionally, if you are working with Event in editor script, and actually have code that Use()s the GUI event, chances are the event might not get through to unity's undo system. You might need to call Undo.FlushUndoRecordObjects(); after RegisterCompleteObjectUndo()

.

Don't forget to use EditorUtility.SetDirty() on the object, AFTER you've recoreded into Undo

Here is an example, allowing me to offset 2D viewport up/down left/right:

 void DragViewingArea() {
             int id = GUIUtility.GetControlID(6, FocusType.Passive);
  
             //prepare for drag:
             if (_currentEvent.type == EventType.MouseDown && _currentEvent.button == 2) {
                 Undo.RegisterCompleteObjectUndo(_fsm, "begin drag FSM viewport on " + _fsm.gameObject.name);
                 Undo.FlushUndoRecordObjects();
              
                 GUIUtility.hotControl = id;
                 _currentEvent.Use();
                 return;
             }
  
             //drag:
             if(_currentEvent.type == EventType.MouseDrag && GUIUtility.hotControl == id) {
                 _fsm._fsmEditorWindow_TableOffset += _currentEvent.delta;
                 _currentEvent.Use();
                 EditorUtility.SetDirty(_fsm); //set dirty as soon as we actually drag
  
                 return;
             }
  
             //finished dragging
             if (_currentEvent.type == EventType.mouseUp  &&  _currentEvent.button == 2  &&  GUIUtility.hotControl == id) {
                 GUIUtility.hotControl = 0;
                 _currentEvent.Use();
                 return;
             }
  
         }


Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
0

Answer by PicklesIIDX · Nov 02, 2016 at 03:32 PM

Since working on this I've learned about the command pattern, which, in this example (http://gameprogrammingpatterns.com/command.html), talks about a method to implement undo with a lot more control.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Welcome to Unity Answers

The best place to ask and answer questions about development with Unity.

To help users navigate the site we have posted a site navigation guide.

If you are a new user to Unity Answers, check out our FAQ for more information.

Make sure to check out our Knowledge Base for commonly asked Unity questions.

If you are a moderator, see our Moderator Guidelines page.

We are making improvements to UA, see the list of changes.



Follow this Question

Answers Answers and Comments

7 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Unity HUB can't able to detect manually downloaded Unity Version 0 Answers

Constant error : SaveSnapshot called without previous call to MakeSnapshot 1 Answer

Unity 5.3 creating Editor folder for normal Scripts by default 0 Answers

Catch a unity undo/redo event. 2 Answers

Black bar on right side of screen 0 Answers

  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges