Serializing is the process in which a binary object (i.e. a class or a struct) is converted to a binary stream. Deserializing is the process in which an object is created from a binary stream. In layman’s terms, this is just a fancy schmancy way to say that a class or a struct is Saved and then Loaded from memory (be it virtual memory or an actual file on disk).
Warning: This does not necessarily mean that serialization is only used when you’re trying to save or load the game to or from a disk! If you’re not planning to have that in your game, it doesn’t make this article any less important. The fact is, Unity serializes and deserializes data all the time, so you have already used serialization whether you’ve realized it or not. And, if you don’t know what you’re doing, serialization can fail when you need to use custom classes (or even many default .NET classes), leading to very strange errors which seemingly come out of nowhere.
There are countless of resources online about serialization in Unity, but I’ll try to cover everything in a series of articles. However, instead of simply providing solutions to problems (some of which will be original), I will also attempt to explain in great detail why things happen the way they do and how exactly the solutions provided achieve the desired behaviour.
I realized that this series of articles was needed after my article on using Unity components as keys in collections. For this introductory article, I will outline various different cases in which Unity serializes and deserializes data.
A lot of the following information was taken almost directly directly from a post on Unity’s blog, although I have included some additional information and chose to go with some slightly less technical explanations that – in my opinion – are easier to understand. I would still advice to check the original post anyway, as it provides some additional details you might want to know about.
When does Unity serialize/deserialize?
Unity needs to serialize or deserialize data in the following circumstances:
- Storing data stored in your scripts. This is used when you change something in the inspector. Those values need to be stored somehow, and they’re stored as serialized instances of your script.
- Inspector window. This applies for the default inspector and not custom inspector editors of classes. In order for the inspector to know which fields should display and what values to attribute to those fields, Unity serializes your scripts and then reads that data.
- Prefabs are serialized GameObjects.
- Instantiation by calling Instantiate() on anything that derives from UnityEngine.Object (such as a GameObject or a MonoBehaviour), serializes an object and then creates a new object with the serialized data.
- Saving/Loading a scene, which contains serialized data of GameObjects.
- Hot Reloading of editor code happens when we change our code while the game is running. The process (in simple terms) is that everything is serialized and then destroyed, before unloading the old code. Then the new code is reloaded, and everything that was serialized is now deserialized and re-created.
- Unity’s asset garbage collector. This is different from the normal garbage collector, and it is used to determine which assets to load and which to unload (like, for example, textures) when a scene changes. It works by reading references from the serialized data of both scenes.
What does Unity serialize?
Unity only serializes the following fields:
- Fields of a serializable field type.
- Fields that are public .
- Fields with a [SerializeField] attribute.
- Fields that are NOT declared static , const or readonly .
- Fields of custom classes that are not equal to null – some kind of a default object will be serialized instead.
Unity considers the following types to be serializable:
- Objects that derive from UnityEngine.Object.
- Structs and classes with a [Serializable] attribute (set immediately before the declaration of the struct or class).
- Non-abstract, non-generic classes, although classes may inherit from generic or abstract classes.
- List<T> where T is a serializable type. This is an exception to the rule on generic classes.
- Primitive types ( char , short , int , float , double , bool , etc ) and string.
- Arrays of serializable types.
Contrary to normal C# serialization, you will not get any exceptions when serializing fields that cannot be serialized. If I have a serializable class, for example, which contains a field type that cannot be serialized, my original class will still be serialized, but my non-serializable fields will not.
public class SerialClass
private int _someNumber;
private NonSerializedType _someField;
//.... more code
After serialization/deserialization _someNumber will retain its value, but _someField will not – it will be null no matter what the previous value was.
For this reason, it is important to thoroughly check your classes and make sure that everything serializes.
What if I don’t serialize?
If you decide to use custom classes that only contain strings or primitive fields, it’s easy to serialize them with the [Serializable] attribute and the [SerializeField] attribute for private fields. If your classes go beyond that, you might find serialization to be too daunting.
If you opt out of serialization and instantiate your custom classes during gameplay, rather than in the editor, your game will still look like it runs perfectly. However, you will not be able to change your code while the game is running. If you do, then you’ll be greeted with many NullReferenceExceptions. If your game is small enough, you might not think that’s a big issue and still get away with it.
More problems will appear when you want to use a custom editor to edit data that belongs in non-serializable classes – those changes will simply fail to be saved and will vanish as soon as you leave the inspector of that editor, or as soon as you press the Play button.
The original post by Unity explains some of the intricacies you need to know, but I will explain and cover them all in upcoming articles. Note that my articles are not meant to copy or replace any existing documentation, but to provide additional insight.