Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • 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
9
Question by Demigiant · Mar 07, 2011 at 09:33 AM · editor-scriptingcustom-inspectorundo

How to correctly register Undos in Custom Inspector (within OnInspectorGUI)?

Hello,

I'm trying to correctly register undos in a custom inspector, and it doesn't look that simple.

How it should be used (C#)

In theory, it should be simple:

Undo.RegisterUndo( (MyClass) target, "Undo name" );

When you call RegisterUndo, all the properties of the given object are registered, so that they can be succesfully undone.

The problem

RegisterUndo should be called immediately BEFORE a property is going to be changed. But how can you know it? Also, if for example you have an IntField, and you drag its value, RegisterUndo should be called only before starting the drag, and not each time the value changes. How do you achieve this?

Possible (non-working) solutions I investigated

  • I tried to register undos when a user presses the left mouse button on a GUI control (using GUIUtility.hotControl), but hotControl changes even if you press other Inspectors of the same object (plus, there's no way to determine if you actually changed something after clicking on it - apart using tempVars for each GUI control, which looks terribly verbose)
  • Using tempVars for each GUI control. But, apart from it being terribly verbose, dragging an IntField (or else) value breaks it.

The C# solution (thanks to mr. Statement)

EDIT: I created a helper class and placed it on Unify. See the "correct answer" for a link.

Working on mr. Statement answer, I found the correct solution. This solution registers all undos correctly, and prevents registering multiple or useless undos (checking if, after the left mouse button was pressed, something actually changed). Also, it works even if you switch field using the TAB key.

Here is an example Class, and its relative custom Inspector.

Sample Class

using UnityEngine; using System.Collections;

public class HOEXInspectorUndo : MonoBehaviour { public enum SampleEnum { Enum_A, Enum_B, Enum_C, Enum_D } public int sampleInt = 0; public int sampleInt2 = 0; public float sampleFloat = 10.52f; public bool sampleBool = true; public SampleEnum sampleEnum = SampleEnum.Enum_A; }

Sample Class's custom Inspector

using UnityEditor; using UnityEngine; using System.Collections;

[CustomEditor( typeof( HOEXInspectorUndo ) )] public class HOEXInspectorUndoEditor : Editor { private HOEXInspectorUndo src; private bool listeningForGuiChanges; private bool guiChanged;

 private void OnEnable()
 {
     src = target as HOEXInspectorUndo;
 }

 override public void OnInspectorGUI()
 {
     CheckUndo();

     src.sampleInt = EditorGUILayout.IntField( "Sample Int", src.sampleInt );
     src.sampleInt2 = EditorGUILayout.IntSlider( "Sample Slider", src.sampleInt2, 0, 100 );
     src.sampleFloat = EditorGUILayout.FloatField( "Sample Float", src.sampleFloat );
     if ( GUILayout.Button( "Set Sample Float to 17.2" ) ) {
         guiChanged = true;
         src.sampleFloat = 17.2f;
     }
     src.sampleBool = EditorGUILayout.Toggle( "Sample Bool", src.sampleBool );
     src.sampleEnum = ( HOEXInspectorUndo.SampleEnum ) EditorGUILayout.EnumPopup( "Sample Enum", src.sampleEnum );

     if ( GUI.changed ) {
         guiChanged = true;
     }
 }

 private void CheckUndo()
 {
     Event e = Event.current;

     if ( e.type == EventType.MouseDown && e.button == 0 || e.type == EventType.KeyUp && ( e.keyCode == KeyCode.Tab ) ) {
         // When the LMB is pressed or the TAB key is released,
         // store a snapshot, but don't register it as an undo
         // ( so that if nothing changes we avoid storing a useless undo)
         Debug.Log( "PREPARE UNDO SNAPSHOT" );
         Undo.SetSnapshotTarget( src, "HOEXInspectorUndo" );
         Undo.CreateSnapshot();
         Undo.ClearSnapshotTarget();
         listeningForGuiChanges = true;
         guiChanged = false;
     }

     if ( listeningForGuiChanges && guiChanged ) {
         // Some GUI value changed after pressing the mouse.
         // Register the previous snapshot as a valid undo.
         Debug.Log( "REGISTER UNDO" );
         Undo.SetSnapshotTarget( src, "HOEXInspectorUndo" );
         Undo.RegisterSnapshot();
         Undo.ClearSnapshotTarget();
         listeningForGuiChanges = false;
     }
 }

}

Comment
Add comment · Show 9
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 Jessy · Mar 07, 2011 at 12:25 PM 0
Share

I haven't had time to look at this yet; this looks like what I tried and had no success with, and it looks like you've already been through this. I will try again later. http://answers.unity3d.com/questions/44552

avatar image Demigiant · Mar 07, 2011 at 12:32 PM 0
Share

Yep, I had seen your post but I was looking for better solutions :) If you find anything please let me know, and thanks :)

avatar image Lf3T-Hn4D · Mar 29, 2011 at 11:59 AM 0
Share

Hey, I'm having the same problem too. It baffles me how something basic such as this is not even documented. How is one supposed to even make good custom tools for unity editor if this isn't even possible? $$anonymous$$y artists are going to kill me for this disability. lol

avatar image Demigiant · Mar 29, 2011 at 12:11 PM 0
Share

Yup, same for me: the artist working with me was not very happy when I told him "no undo for this and that"! :P

avatar image Jessy · Apr 18, 2011 at 03:10 AM 0
Share

The first ClearSnapshotTarget() allows for multiple changes to the same control to be undone. What is the point of the later one?

Show more comments

4 Replies

· Add your reply
  • Sort: 
avatar image
0
Best Answer

Answer by Demigiant · Nov 27, 2011 at 06:09 PM

Complete class to correctly manage undo

posted it on Unify. You can find it here.

Comment
Add comment · Show 4 · 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 sbuzonas · Oct 10, 2017 at 11:10 PM 1
Share

Link only answers are bad. Now there is a 404 at the link.

avatar image Bunny83 sbuzonas · Oct 11, 2017 at 05:19 AM 0
Share

I've fixed the wiki link. However the wiki seems to be currently down. I've found this class which seems to be based on the script posted on the wiki (according to the comment at the top)

avatar image Demigiant Bunny83 · Oct 11, 2017 at 09:22 AM 0
Share

Ah, that is indeed my old script, though it's very old now and newer Unity versions work differently and with easier undo logic :P

Show more comments
avatar image
2

Answer by Statement · Apr 02, 2011 at 04:20 PM

RegisterUndo should be called immediately BEFORE a property is going to be changed. But how can you know it?

It sounds like you aren't responding to button clicks etc, but poll some continous value. Otherwise it would be pretty trivial:

if (GUILayout.Button("Do something")) {
    Undo.RegisterUndo(obj, "Do Something " + obj.name);
    obj.position = Vector3.zero;
}

Also, if for example you have an IntField, and you drag its value, RegisterUndo should be called only before starting the drag, and not each time the value changes. How do you achieve this?

From the reference:

Certain operations, such as dragging, consist of many small incremental changes. Typically it is not desired to create an undo step for each of these small changes. For example, if the user performs an undo after a dragging operation, it is expected that the object is reverted back to the state it had before the dragging started. The functions SetSnapshotTarget, CreateSnapshot, and RegisterSnapshot are available to handle cases like this.


I suggest you Check out this similar question with a slider control.

Example snippet from question:

using UnityEditor; using UnityEngine;

public class Example : EditorWindow {

 [SerializeField] float sliderValue = 1f;

 [MenuItem("Example/Slider Test")]
 static void Init() {
     var myWindow = (Example)EditorWindow.GetWindow(typeof(Example));
     myWindow.autoRepaintOnSceneChange = true;
 }

 void OnGUI() {
     checkMouse();
     GUILayout.BeginVertical("box");
     GUILayout.Label("Test Slider");
     sliderValue = EditorGUILayout.Slider(sliderValue, .1f, 5f);
     GUILayout.EndVertical();
     Undo.ClearSnapshotTarget();
     this.Repaint();
 }

 void checkMouse() {
     Event e = Event.current;
     if (e.button == 0 && e.isMouse) {
         Undo.SetSnapshotTarget(this, "Changed Slider");
         Undo.CreateSnapshot();
         Undo.RegisterSnapshot();
     }
 }

}

Comment
Add comment · Show 4 · 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 Demigiant · Apr 03, 2011 at 09:27 AM 0
Share

First of all thanks for the detailed answer, Statement :) I had totally misunderstood the "setSnapshotTarget" concept. I'll check with your way and see how it goes. About the "Undo.RegisterUndo" thing: I have almost no buttons in my inspectors. Ins$$anonymous$$d, I have many toggles, fields, and such.

avatar image Demigiant · Apr 03, 2011 at 10:16 AM 0
Share

Your method doesn't seem to work with an Editor ins$$anonymous$$d than an EditorWindow (thus placed within OnInspectorGUI). I built a simple example, and you can find it here: http://www.holoville.com/appo/InspectorUndo.zip - any chance you might take a look at it and see if I'm doing something stupid? I also tried to use the "Check$$anonymous$$ouse" method from the above snippet, but that doesn't work either.

avatar image Demigiant · Apr 03, 2011 at 10:17 AM 0
Share

Sorry, miswritten link. The correct example is here: http://www.holoville.com/appo/_keep/InspectorUndo.zip

avatar image Statement · Apr 03, 2011 at 11:58 AM 0
Share

See my new answer dedicated for your current code base.

avatar image
2

Answer by Statement · Apr 03, 2011 at 10:53 AM

Dear Daniele, I downloaded your InspectorUndo.zip and manage to solve your problem!

You only need to change one line of code...

  • Undo.SetSnapshotTarget(this, "HOEXInspectorUndo");
    to ...
  • Undo.SetSnapshotTarget(src, "HOEXInspectorUndo");

... since you want to save the values of your target, not the values of your inspector. Below is the full editor code for completeness.

using UnityEditor; using UnityEngine;

[CustomEditor(typeof(HOEXInspectorUndo))] public class HOEXInspectorUndoEditor : Editor { private HOEXInspectorUndo src;

 private void OnEnable()
 {
     src = target as HOEXInspectorUndo;
     EditorApplication.modifierKeysChanged += this.Repaint;
 }

 private void OnDisable()
 {
     EditorApplication.modifierKeysChanged -= this.Repaint;
 }

 override public void OnInspectorGUI()
 {
     Undo.SetSnapshotTarget(src, "HOEXInspectorUndo");
     src.sampleInt = EditorGUILayout.IntField("Sample Int", src.sampleInt);
     if (GUI.changed)
     {
         Undo.CreateSnapshot();
         Undo.RegisterSnapshot();
     }
     Undo.ClearSnapshotTarget();
 }

}

Comment
Add comment · Show 5 · 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 Demigiant · Apr 03, 2011 at 02:58 PM 0
Share

Ooops, now I feel really dumb L Thanks Statement, that was a stupid mistake on my side. Now it works almost perfectly :) Only issue is: setting Undo.CreateSnapshot on GUI.changed stores the snapshot the first time the gui changes. Thus, if you drag the IntField slider to the right, undoing goes back to a slightly higher value than the initial one. Dragging left ins$$anonymous$$d undoes to a lower value. Anyway, now that I understood the concept I'll work on it some more, and try to find the correct way to register the snapshot - and then grant you the deserved 100 rep :)

avatar image Demigiant · Apr 03, 2011 at 04:44 PM 0
Share

If anyone needs the full solution, look at my answer. I edited it adding a sample Class and its Inspector. Statement's solution doesn't work correctly, but he was very detailed and greatly helped me to understand the SetSnapshotTarget concept and how undo works. Thus I'm marking his answer as the correct one, so he can grab the bounty :) Thanks again Statement :)

avatar image Statement · Apr 03, 2011 at 05:50 PM 0
Share

You could always write an answer and accept it after 24h.

avatar image Demigiant · Apr 03, 2011 at 08:13 PM 0
Share

You're a true gentleman ;) Anyway, it's too late: you got bounty :D

avatar image Demigiant · Nov 28, 2011 at 08:40 AM 0
Share

Hey Statement, I made a full class and set it as the correct answer. I hope that after all this time the points stayed with you :)

avatar image
2

Answer by numberkruncher · Jul 04, 2012 at 02:50 PM

This is what SerializedObject and SerializedProperty are supposed to make easier. I have not tested the following, but it should work :-)

 using UnityEngine;
 
 [CustomEditor(typeof(HOEXInspectorUndo))]
 public class HOEXInspectorUndoEditor : Editor {
 
     private HOEXInspectorUndo src;
 
     private SerializedProperty _prop_sampleInt;
     private SerializedProperty _prop_sampleInt2;
     private SerializedProperty _prop_sampleFloat;
     private SerializedProperty _prop_sampleBool;
     private SerializedProperty _prop_sampleEnum;
     
     private void OnEnable() {
         src = target as HOEXInspectorUndo;
         
         _prop_sampleInt = serializedObject.FindProperty("sampleInt");
         _prop_sampleInt2 = serializedObject.FindProperty("sampleInt2");
         _prop_sampleFloat = serializedObject.FindProperty("sampleFloat");
         _prop_sampleBool = serializedObject.FindProperty("sampleBool");
         _prop_sampleEnum = serializedObject.FindProperty("sampleEnum");
     }
 
     override public void OnInspectorGUI() {
         serializedObject.Update();
 
         _prop_sampleInt.intValue = EditorGUILayout.IntField("Sample Int", src.sampleInt);
         _prop_sampleInt2.intValue = EditorGUILayout.IntSlider("Sample Slider", src.sampleInt2, 0, 100);
         
         _prop_sampleFloat.floatValue = EditorGUILayout.FloatField("Sample Float", src.sampleFloat);
         if (GUILayout.Button("Set Sample Float to 17.2")) {
             _prop_sampleFloat.floatValue = 17.2f;
             EditorGUIUtility.ExitGUI();
         }
         _prop_sampleBool.boolValue = EditorGUILayout.Toggle( "Sample Bool", src.sampleBool );
         _prop_sampleEnum.enumValueIndex = (int)(HOEXInspectorUndo.SampleEnum)EditorGUILayout.EnumPopup("Sample Enum", src.sampleEnum);
 
         if (GUI.changed)
             serializedObject.ApplyModifiedProperties();
     }
 
 }

For further information see: http://docs.unity3d.com/Documentation/ScriptReference/Editor.html

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.

Follow this Question

Answers Answers and Comments

8 People are following this question.

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

Related Questions

[Editor Scripting] Call Function in other GameObjects Editor Script? 0 Answers

Custom Editor for Monobehavior with custom property not retaining Gameobject references 0 Answers

Custom Inspector: Making Individual Vertical Containers and Dynamically Adding & Removing Child Elements 1 Answer

Responsive Editor UI Button with custom style | How to remove GUIStyle.hover delay 0 Answers

OnInspectorGUI changes reset when played in editor or building 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges