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
2
Question by Filip Van Bouwel · Dec 06, 2012 at 05:43 PM · editorserializationundoedit mode

Catch a unity undo/redo event.

Say I have a script like this :

 [ExecuteInEditMode]
 public class RandomScript : Monobehaviour
 {
     //Private array.
     [SerializeField]
     private float[] _numbers;

     //Public property so I'm sure that DoSomething() is executed when someone sets the float array.
     public float[] Numbers
     {
         get { return _numbers; }
         set
         {
             _numbers = value;
             DoSomething();
         }
     }

     //Some heavy calculations that need to be done after adjusting the float array.
     DoSomething()
     {
         //Initialize stuff from _numbers;
     }
 }

}

I put the field private, but serializable, so unity remembers the values between sessions, and a public property to make sure stuff is done after the float array is adjusted. In my custom inspector script I implemented an undo state (Undo.RegisterUndo(targetObject, "Description")). Now, whenever the user selects the undo operation in unity, unity puts the old float array back in the private field (so it doesn't use the public property), so the DoSomething() method isn't called and my other data isn't updated after the undo. Same for redo.

The only solution is see atm is calling DoSomething() in every update, but that's a heavy operation (and bad code design in the first place).

Is there any way to catch an undo or redo event from unity? Some event to subscribe to or a method to implement that unity will call (like Awake(), Start(), OnEnable(), ...)?

Comment
Add comment
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

2 Replies

· Add your reply
  • Sort: 
avatar image
4

Answer by MrPhil · Jun 24, 2013 at 11:53 PM

I see two approaches you can take.

First, is the a no CustomEditor approach which I think is better because it involves less code:

 using UnityEngine;
 using System.Collections;
 
 [ExecuteInEditMode]
 public class RandomScript : MonoBehaviour
 {
     //Private array.
     [SerializeField]
     public float[] _numbers;
 
     //Public property so I'm sure that DoSomething() is executed when someone 
     //sets the float array.
     public float[] Numbers
     {
         get { return _numbers; }
         set
         {
             _numbers = value;
             DoSomething();
         }
     }
 
     void Update()
     {
         EditorUpdate();
     }
 
     //Some heavy calculations that need to be done after adjusting 
     //the float array.
     public void DoSomething()
     {
         //Initialize stuff from _numbers;
         Debug.Log("DoSomething() called!");
     }
 
     public void EditorUpdate()
     {
 #if UNITY_EDITOR // Only when in the editor
         if (!Application.isPlaying)
         {
             // It is possible my numbers changed
             DoSomething();
         }
 #endif
     }
 }

By using the Preprocessor Directive #if UNITY_EDITOR you make sure the code is removed from builds. The check for Application.isPlaying make sure the code is run only due to an Update call by the Editor:

Update is only called when something in the scene changed. - ScriptReference: ExecuteInEditMode

So, it isn't as heavy handed as it looks.

Second approach uses the Event types to look for the special Undo/Redo command, see bottom of the OnInspectorGUI() method:

 using UnityEditor;
 using UnityEngine;
 
 [CustomEditor(typeof(RandomScript))]
 public class RandomScriptEditor : Editor
 {
     // Our serialized array of floats on the RandomScript
     public SerializedProperty _numbers;
 
     // This is for formatting in the Editor
     private static GUILayoutOption[] noneGUILayoutOption =
         new GUILayoutOption[] { };
 
     // This function is called when the object is loaded.
     void OnEnable()
     {
         // Get the property from the editing object
         _numbers = serializedObject.FindProperty("_numbers");
     }
 
     // Implement this function to make a custom inspector.
     public override void OnInspectorGUI()
     {
         // Update the serializedProperty 
         //  - always do this in the beginning of OnInspectorGUI.
         serializedObject.Update();
 
         // How you change the size of the array
         _numbers.arraySize =
             EditorGUILayout.IntField("Size", _numbers.arraySize);
 
         // Display each element/number in the array
         foreach (SerializedProperty numberField in _numbers)
         {
             Rect rect = GUILayoutUtility.GetRect(0f, 16f);
             EditorGUI.PropertyField(rect, numberField);
         }
 
         // Apply changes to the serializedProperty 
         //  - always do this in the end of OnInspectorGUI.
         // Checking the Event type lets us update after Undo and Redo commands.
         if (serializedObject.ApplyModifiedProperties() ||
             (Event.current.type == EventType.ValidateCommand &&
             Event.current.commandName == "UndoRedoPerformed"))
         {
             // Tell our target to update because it's properties have changed
             ((RandomScript)target).DoSomething();
         }
     }
 }

For a nice detailed tutorial on Custom Editor's check out Catlike Coding's Star tutorial.

Comment
Add comment · Show 3 · 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 Filip Van Bouwel · Nov 07, 2013 at 03:11 PM 0
Share

That second method looks great. Same for the tutorial. Gonna play around with it a bit.

Thanks!

avatar image tarasfromlviv · May 14, 2016 at 09:16 PM 3
Share

Note in Unity 5 it is EventType.ExecuteCommandnot Event.current.type == EventType.ValidateCommand, just for those who get here and wonder why its not working.

avatar image a52 · Feb 04 at 03:00 AM 0
Share

Second method deprecated as of some time in Unity 5 -- Undo and Redo no longer seem to be stored by the Event class. The current way to do it seems to be to add a method of yours to the Undo.undoRedoPerformed callback. Note that it seems to throw an exception if the method added belongs to the GameObject -- make a wrapper for it in your editor class instead. (This also keeps all the editor-only stuff conveniently in your editor class, letting you keep the GameObject purely for game logic.)

avatar image
0

Answer by lPVDl · Sep 13, 2018 at 06:38 PM

If you are still interested in different solution (which seems to be added in latest versions of Unity), then here is an another approach: Implement ISerializationCallbackReceiver to receive callback whenever component is serialized or deserialized:

 [ExecuteInEditMode]
 public class RandomScript : MonoBehaviour, ISerializationCallbackReceiver
 {
     //Private array.
     [SerializeField]
     private float[] _numbers;
     //Public property so I'm sure that DoSomething() is executed when someone sets the float array.
     public float[] Numbers
     {
         get { return _numbers; }
         set
         {
             _numbers = value;
             DoSomething();
         }
     }
 
     // Whenever object is deserialized.
     void ISerializationCallbackReceiver.OnAfterDeserialize()
     {
         DoSomething();
     }
 
     //Some heavy calculations that need to be done after adjusting the float array.
     private void DoSomething()
     {
         //Initialize stuff from _numbers;
     }
 
     void ISerializationCallbackReceiver.OnBeforeSerialize() { }
 }

The sad thing that it will not work in some situations, for example if you need to update object's transform position after deserialization, this will not work, since it is restricted to do. alt text In this case you can create flag something like "RequireUpdate" and call you method in Update() after checking this flag, the disadvantage of this solution is that you will need to create Update(), which will allow game designer to disable your component, which is not awesome. Recently I've used this trick: Create class which will implement "Lazy Actions", which will be called next frame and only in the editor:

 using System;
 using System.Collections.Generic;
 using UnityEngine;
 #if UNITY_EDITOR
 using UnityEditor;
 #endif
 
 public static class EditorLazyAction
 {
     private static List<Action> Actions = new List<Action>();
 
     static EditorLazyAction()
     {
         #if UNITY_EDITOR
         EditorApplication.update -= Update;
         EditorApplication.update += Update;
         #endif
     }
 
     private static void Update()
     {
         foreach (var action in Actions)
         {
             try
             {
                 action.Invoke();
             }
             catch (Exception ex)
             {
                 Debug.LogException(ex);
             }
         }   
         Actions.Clear();
     }
 
     public static void Add(Action action)
     {
         if (action != null)
             Actions.Add(action);
     }
 }

Than you can use it this way:

 [ExecuteInEditMode]
 public class RandomScript : MonoBehaviour, ISerializationCallbackReceiver
 {
     //Private array.
     [SerializeField]
     private float[] _numbers;
     //Public property so I'm sure that DoSomething() is executed when someone sets the float array.
     public float[] Numbers
     {
         get { return _numbers; }
         set
         {
             _numbers = value;
             DoSomething();
         }
     }
 
     // Whenever object is deserialized.
     void ISerializationCallbackReceiver.OnAfterDeserialize()
     {
         EditorLazyAction.Add(DoSomething);
     }
 
     //Some heavy calculations that need to be done after adjusting the float array.
     private void DoSomething()
     {
         //Initialize stuff from _numbers;
     }
 
     void ISerializationCallbackReceiver.OnBeforeSerialize() { }
 }

No flags, no updates in custom class - less code and no way to "disable" initialization by disabling component.


rxbg3or.png (5.4 kB)
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

13 People are following this question.

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

Related Questions

Custom data for undo/redo action? 0 Answers

Can my EditorWindow intercept Undo Redo Keyboard commands? 0 Answers

Using Undo.RecordObject() does not protect a component's variables from being reverted to the prefab on play. 0 Answers

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

undo redo + custom serialization slow 0 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