Unity Serialization and persistency
Unity Serialization is pretty good for primitive datatypes, as far a I know, but if you use it for bigger things it as some fundamental flaws. (ex. http://www.reddit.com/r/Unity3D/comments/2e9vlg/unity_serialization_is_truly_fubar/, http://blogs.unity3d.com/2014/06/24/serialization-in-unity/, etc.)
In my case I wanted to have a connection of a component and a file and it should be persistent. The InstanceID of an Component (aswell as from the GameObject) isn’t any use because it is different every a scene loads.
Unity builtin persistency connects GameObjects and it’s component to the scene file via the “m_LocalIdentfierInFile” (you can see it if you change the inspector view to Debugmode). I like the misspelling… I wonder if it will be fixed in Untiy 5? :)
So I figured there must be a way to use the same part for my case. A main problem is that the UnityEngine doesn’t expose how it works and as far as I could material on the i-net on it, it’s done on the c++ side of the engine.
Long story short: After trying many things I came up with a simple workaround, which is creating a copy of the “m_LocalIdentfierInFile” while being in the UnityEditor and saving it as serialized property.
Spoiler: It won’t work for GameObjects / Components which are created during runtime.
And here is how you can do it too:
// This is a copy of the "m_localIndentiferInFile"
// (don't forget to use [Serializable] on the class)
[SerializeField]
private int persistentID = -1;
With the following snippet you can access the “m_LocalIdentfierInFile”. Make sure it is surrounded with the “#if UNITY_EDITOR” and don’t use “using UnityEditor;” otherwise you can’t create a build!
// Init this instance, it's public so it can be called from a InspectorScript
// is only set via the unity editor
public void init ()
{
#if UNITY_EDITOR
PropertyInfo inspectorModeInfo =
typeof(UnityEditor.SerializedObject).GetProperty ("inspectorMode",
BindingFlags.NonPublic | BindingFlags.Instance);
UnityEditor.SerializedObject serializedObject =
new UnityEditor.SerializedObject (comp);
inspectorModeInfo.SetValue (serializedObject, UnityEditor.InspectorMode.Debug, null);
UnityEditor.SerializedProperty localIdProp =
serializedObject.FindProperty ("m_LocalIdentfierInFile")
//Debug.Log ("found property: " + localIdProp.intValue);
persistentID = localIdProp.intValue;
//Important set the component to dirty so it won't be overriden from a prefab!
UnityEditor.EditorUtility.SetDirty (this);
#endif
}
(This snippet was provided by “thelackey3326” in this UnityForum post. ) With the addition of the “UnityEditor.EditorUtility.SetDirty (this);” line, very important to prevent prefabs from overriding the persistentID!
Here an InspectorScript snippet which uses OnEnable() to call the init() method.
public void OnEnable ()
{
myPersist.init ();
}
This only works once the scene is saved (before there is no “m_LocalIdentfierInFile” meaning it is == 0) and it also means, you have create a gameobject (or drag’n’drop a prefabe), save the scene and click at least ONCE on the Gameobject!
Usually if you drag an Prefab into a scene you will at some point click on it and do something with it, but it’s not a very nice workflow.
There is the possibility to hook in when the scene is being saved, unfortunately Unity doesn’t provide a function to hook in after a scene was saved. So here’s a code snippet to call the init() function before a scene is saved.
static string[] OnWillSaveAssets (string[] paths)
{
Object[] objs = Component.FindObjectsOfType (typeof(PersistObject));
//Debug.Log ("OnWillSaveAssets " + objs.Length);
foreach (Object obj in objs) {
PersistObject persist = (PersistObject)obj;
persist.init ();
}
return paths;
}
(Find the full script on my Gist it’s specific for my implementation so copy/paste won’t work directly, but you can see how it works. The main thing is that you inherit from “AssetModificationProcessor”.)
Using that hook means: if you create an GameObject which uses the Editor-Snippet to get the “m_LocalIdentfierInFile” you have to save the scene at least twice after that. First time to set the “m_LocalIdentfierInFile” by Unity itself and second to store it in the local variable. It is not the most convenient way, but good enough, just setup a scene with the persistent GameObject first.
Was my nerd work useful to you?
Please consider a small donation so I can get some fuel aka. coffee to dig into further programming problems: