Have been looking into topics like Serialization and attributes such as FormerlySerializedAs recently, and am wondering about how people might approach maintaining serialized data when a field’s type has been refactored.
Let’s say we start with the following code
class SomeComponent : MonoBehaviour {
public float SomeFloatValue = 123f;
}
At some point down the track you realise that you need to change that property to a different type, perhaps a serializable class you’ve created:
public class FloatRandomiser {
public FloatRandomiser(float default) {
value = default;
}
public float value = 0f;
public float valueWithRandom { get { return value + Random.Range(-1f, 1f); } }
}
You couldn’t just change it to:
public FloatRandomiser SomeFloatValue = new FloatRandomiser(123f);
Any previously serialized data wouldn’t fit.
You could add extra behaviour to the Start/Awake function to set values accordingly. Even better if the MonoBehaviour used [ExecuteInEditMode] as those new values would be serialized when the scene is saved. EG:
class SomeComponent : MonoBehaviour {
[FormallySerializedAs("SomeFloatValue")]
[SerializeField][HideInInspector] private float _SomeFloatValue = 123f;
public FloatRandomiser SomeFloatValue = new FloatRandomiser(123f);
public Awake() {
SomeFloatValue.value = _SomeFloatValue;
}
}
This wouldn’t catch any cases where scenes with those components aren’t opened and saved howoever. (Actually, I’m not sure if using the same name in that example would cause other issues…)
I’m curious to know if there are any approaches or serialization options that can help with these sorts of transitions?
Thanks!
So I don’t know the unity serialization interface in depth, and I would love to be schooled on more specifics.
But as far as I know, unity suffers from a lack of a robust interface to do what you ask. What I usually do is if I have to change out types is I implement the ISerializationCallbackReceiver. I then define the replacement field with a new name. And then during the OnAfterDeserialize event I set the old value to what it should be.
After a few iterations of this, I will deprecate it, and then remove, once I know that all my instances of the script have been touched in some degree and the serialized data has been updated.
Another method I use is writing an explicit script that touches all those obejcts. Basically allowing me to touch all of them, causing them to deserialize into memory, and update their data. And after doing so remove the ISerializationCallbackReceiver.
Another option, which I haven’t tested, is create an implicit cast method on the new type that converts the old type to the new type. I haven’t tried this, but it might work.
It would be nice if Unity had some type of interface for this. I wish that they implemented their ISerializationCallbackReceiver so that it passed in the serialized data reader for you to pull the data from yourself. Like how the built in .Net serialization works… but alas, it does not. Grrrrrrrrrr.
Thanks for the reply.
I really like the idea of implicit casting, might look in to that.
If that doesn’t work, I wonder how practical (and safe) it would be to have a “run once” method which would go through the entire Asset Database and update the values for any relevant components as required. Thinking in the case of a package/plugin being used by other external game devs…
Definitely some room for improvement with this sort of stuff, but can definitely see how it would be a massive challenge from the Unity side as well.
Just ran a real quick test with implicit casting (and explicit even though that is guaranteed not to work). Although no errors were thrown it seems that implicit casting never actually happened. Any Debug.Log(…) in the implicit casting was never called.
I know the implicit cast is happening because the following works when in a method:
float someFloat = 123f;
RandomisedFloat randomisedFloat = someFloat;
I may have done something wrong so I’ll double check again later, but for now it doesn’t look good.
Suspect Unity’s serialize/deserialize actions use reflection or some other mechanism…
probably, was just an idea to see if it worked
note, debug.log won’t work ever in serialization as it occurs off the main thread. Unity will sometimes crash, or otherwise log errors.
Great, thanks for the heads up!