- Home /
Editing and Undo for non-unity properties
How can handle undo for non-unity, not unity serialized properties?
Imagine I was making a .ini editor in unity using a custom EditorWindow. I read the ini file, for each [section] I make a Dictionary
and for each key=value
make an entry in a Dictionary
themselves are in a Dictionary>
.
In OnGui for each section I call EditorGUILayout.Folder
and within each section I call EditorGUILayout.TextField
for each key/value pair.
This all works. I can load an .ini file and I see all the fields. But, a bunch of things don't work and I'm really confused about what Unity expects.
My data gets lost on every single call to OnGUI??? In other words I have something like this
class MyWindow : EditorWindow { Dictionary<string, Dictionary<string, string> > m_sections; void OnGUI() { if (m_sections == null) { Debug.Log("new m_sections"); // This gets called constantly!!! WTF?!?! m_sections = new Dictionary<string, Dictionary<string, string>>(); } ... } }
How do I handle Undo?
I tried making class MyData
that inherits from UnityEngine.Object
and putting my Dictionary>
instead it as a public field. That way I could call Undo.RecordObject
. I can't imagine that will work but since I can't get past #1 above I have no idea.
sigh....this board ate parts of my post. I guess it doesn't like angle brackets in in-line code :(
You shouldn't loose your data unless Unity does a recompile (because you edited a script outside of Unity) or you start / stop the game. What does "constantly" mean to you? OnGUI of an EditorWindow is only called when an event occurs.
Unity can't serialize Dictionaries at all, so you can't use Unity's Undo system. The Undo system is actually ment for undo user actions. If you just don't want to loose your data, store it in a serializable construct.
Could you be more precise what exactly you want to achieve? Do you just want to prevent your data get lost on serialization / deserialization or do you actually want register an undo action the user can revert?
What I mean is on every call to OnGUI m_sections
is null. As for undo, I get that unity can't serialize dictionaries and I've removed them.
What I want to do is present a list of fields for the user to edit. Those fields are deter$$anonymous$$ed at runtime from an .ini file. If the user edits a field I want them to be able to undo their edit. They are global data (not tied to any GameObject or $$anonymous$$onoBehavior).
I found this blog post about serilization which actually doesn't work but lead to some progress. I found this wiki post which is out of date and doesn't work.
Do you know of any working samples of an EditorWindow that edit data not stored in a $$anonymous$$onoBehavior that support undo and persisting after closing and opening the EditorWindow?
Does this help?
Answer by Bunny83 · Nov 26, 2014 at 09:47 PM
To handle temporary data inside the editor that isn't lost when you close you editor windows, change the scene, change playmode or when scripts recompile you want to use a ScriptableObject. It's important to set the hideFlags to DontSave otherwise Unity would try to save it along with the scene. It would even mark the scene as dirty (modified) if you don't set the hideflags.
You usually want to use a singleton like pattern for this ScriptableObject. However keep in mind that static variables are also wiped when scripts are recompiled. So you have to use Resources.FindObjectsOfTypeAll to search for an existing (serialized) instance. Something like this:
public class DataObject : ScriptableObject
{
private static DataObject m_Instance = null;
public static DataObject Instance
{
get
{
if (m_Instance == null)
{
var tmp = Resources.FindObjectsOfTypeAll<DataObject>();
if (tmp.Length > 0)
m_Instance = tmp[0];
else
m_Instance = ScriptableObject.CreateInstance<DataObject>();
m_Instance.hideFlags = HideFlags.DontSave;
}
return m_Instance;
}
}
public string someSerializedData = "Hello World";
}
Keep in mind that ScriptableObjects need it's own file where the file name matches the class name, just like MonoBehaviour scripts.
It's important to either always use the singleton property or get the reference in OnEnable and store it in a variable:
// in your editor window
DataObject data;
void OnEnable()
{
data = DataObject.Instance;
}
void OnGUI()
{
data.someSerializedData = GUILayout.TextField(data.someSerializedData);
}
Note: Unity also serialized private members of editor classes unless you put a NonSerialized attribute on it. So in this case here the reference to our DataObject instance which is stored in the "data" variable will survive playmode changes while the window is open.
Thanks for this. What if I want the data to be associated with the project? In other words I want it to survive across scenes the same as the data for the Physics$$anonymous$$anager or the Input$$anonymous$$anager etc..
@greggman: In this case you have to save your ScriptableObject as asset using the AssetDatabase.CreateAsset. That way you can store the ScriptableObject as asset in the project. The TerrainData (which is also a ScriptableObject) does the same.
Answer by BMayne · Nov 26, 2014 at 02:19 AM
Hey There,
UnityEditor data does not serialize in any way at all.
Everything is temporary (editor stuff)
Whenever you view a editor class (inspector/window) you are looking at a brand new object. As soon as you close that window or change your selection (ie changing the inspector window) that editor window is tossed out with all that data that you think it saved. If you make a public bool it will stick around until you leave the window but if you come back it's a brand new window.
You are going to have to save the data yourself. What that comes down to is saving the data as XML or json (or whatever). You can then take this data and save it to EditorPrefs, a TextFile, a ScriptableObject, or the Userdata of the script itself. The choice is yours but each one has it's own advantages. If you are going simple just save your data to EditorPrefs (keep note this only saves on your computer).
The More You Know!
Unity uses a powerful library that is built into C# to make all these classes. It's called Reflection and the class they use is called Activator. This basically just makes a class on the fly just based on a type.
Thanks for that. I'm not sure what you mean by "Userdata of the script itself". I'm using a ScriptableObject to store my data. I'm using CreateInstance to create it. Undo works. But, closing the window I lose the data.
Here's the thing though. Even Unity's examples don't work? Here's a Unity talk on this issue. Here's a link to the samples from the talk. According to the talk the second example should work. So, download the samples, run any of them. Pick Window->SerializationXX where XX is one of the ones that's supposed to work. Try Serialization02 or Serializatoin07. Edit the fields, close the window, open the window, all data is gone. Having the data persist is specifically what the difference between Serliazation01 and Serialization02 or are supposed to show isn't it?
Any ideas how to fix it?
So I'm kind of maybe starting to get it. Unfortunately I'm still not sure. I need the data to persist past an EditorWindow being closed. And, I need Undo to persist as well. In other words, open EditorWindow, change "name" field from "abc" to "foo". Close window. Pick "undo". Field needs to go back to "abc" just like undoing a property in a component.
This doesn't work currently because the moment I close the EditorWindow all references to my data are gone so although their are entries in the undo stack (the undo menu shows an entry for undoing the last edit that happened in the editor window) that undo does nothing because there is no editor window and therefore no object to apply the undo or at least not one in my control
The undo stack you are making does it effect only editor stuff or engine stuff too?
I'm not making an undo stack. I'm just calling Undo.RecordObject
. I'm not sure what you mean by editor vs engine. The data only needs to exist in the editor at the moment.
@B$$anonymous$$ayne: Your first statement is actually wrong. It's true that editor class aren't serialized to disk because that wouldn't make much sense. However Unity does serialize Editors, EditorWindows and everything else derived from UnityEngine.Object. Unity serializes everything before script compilation destroys all objects, loads the new assemblies and deserialize everything from the serialized data. That happens whenever Unity recompiles scripts or when changing gamemode.
Of course if you close an EditorWindow it's gone. You would have to use a ScriptableObject to store your data. I'll post an answer...