So many questions bundled in one. You should have divided them to small precise questions. So don’t expect a short answer.
Type 1 being when scripts are
recompiled and type 2 being when you
enter/exit play mode.
There’s no such thing as ‘2 types of serializations’ - It’s just that serialization occur in multiple situations, some are editor-only and some are run-time. When you enter/exit play mode, or when you make a modification to one of your scripts and save it an assembly reload happens
What happens when an assembly is
reloaded? When you enter / exit play
mode or change a script Unity has to
reload the mono assemblies, that is
the dll’s associated with Unity.
On the user side this is a 3 step process:
1- Pull all the serializable data out of managed land, creating an internal representation of the data on the C++ side of Unity.
2- Destroy all memory / information associated with the managed side of Unity, and reload the assemblies.
3- Reserialize the data that was saved in C++ back into managed land.
From Unity’s serialization blog post. For more information refer to Tim Cooper’s Unite video on the subject.
What can, what can’t?
From the SerializeField documentation reference:
The serialization system used can do
the following:
- CAN serialize public nonstatic fields (of serializable types)
- CAN serialize nonpublic nonstatic fields marked with the
[SerializeField] attribute.
- CANNOT serialize static fields.
- CANNOT serialize properties.
Your field will only serialize if it
is of a type that Unity can serialize:
Serializable types are:
- All classes inheriting from UnityEngine.Object, for example
GameObject, Component, MonoBehaviour,
Texture2D, AnimationClip… - All basic
data types like int, string, float,
bool. - Some built-in types like
Vector2, Vector3, Vector4, Quaternion,
Matrix4x4, Color, Rect, LayerMask… -
Arrays of a serializable type
- List of a serializable type (new in Unity2.6)
- Enums
I encourage you to get ILSpy, and check out the Unity.SerializationLogic.dll
in the \Unity\Editor\Data\Managed
you’ll see things for yourself and understand them much better.
For example, here’s how Unity determines if a collection type is supported or not:
public static bool IsSupportedCollection(TypeReference typeReference)
{
return (typeReference is ArrayType || CecilUtils.IsGenericList(typeReference)) && (!typeReference.IsArray || ((ArrayType)typeReference).Rank <= 1) && UnitySerializationLogic.IsTypeSerializable(CecilUtils.ElementTypeOfCollection(typeReference));
}
Which means: Only 1-dimensional arrays/lists whose element type is serializable are supported by the serialization system.
Moving on, what determines a serializable type?
private static bool IsTypeSerializable(TypeReference typeReference)
{
return !typeReference.IsAssignableTo("UnityScript.Lang.Array") && (UnitySerializationLogic.IsSerializablePrimitive(typeReference) || typeReference.IsEnum() || UnitySerializationLogic.IsUnityEngineObject(typeReference) || UnityEngineTypePredicates.IsAnimationCurve(typeReference) || UnityEngineTypePredicates.IsGradient(typeReference) || UnityEngineTypePredicates.IsRectOffset(typeReference) || UnityEngineTypePredicates.IsGUIStyle(typeReference) || UnitySerializationLogic.ShouldImplementIDeserializable(typeReference));
}
public static bool WillUnitySerialize(FieldDefinition fieldDefinition, TypeResolver typeResolver)
{
return fieldDefinition != null && !fieldDefinition.IsStatic && !UnitySerializationLogic.IsConst(fieldDefinition) && !fieldDefinition.IsNotSerialized && !fieldDefinition.IsInitOnly && !UnitySerializationLogic.ShouldNotTryToResolve(fieldDefinition.FieldType) && UnitySerializationLogic.IsFieldTypeSerializable(typeResolver.Resolve(fieldDefinition.FieldType)) && !UnitySerializationLogic.IsDelegate(typeResolver.Resolve(fieldDefinition.FieldType)) && (fieldDefinition.IsPublic || UnitySerializationLogic.HasSerializeFieldAttribute(fieldDefinition) || UnitySerializationLogic.ShouldHaveHadAllFieldsPublic(fieldDefinition)) && !(fieldDefinition.FullName == "UnityScript.Lang.Array");
}
As you can see, the “What can, what can’t” is clearly stated in plain text.
Now for serializing things that Unity can’t:
For delegates, see this answer. For dictionaries, you can use the new ISerializationCallbackReciever. If you’re willing to pay, checkout Full Inspector. It uses custom serialization for pretty much everything (pretty much, the best way to go about serialization is to make your own serialization system using protobuf or such)
Here’s a simple way I use to serialize some types that Unity can’t (using BinaryFormatter, but you could modify it easily to use a dif/better serializer):
public static class SerializationHelper
{
/// <summary>
/// Serializes 'value' to a string, using BinaryFormatter
/// </summary>
public static string SerializeToString<T>(T value)
{
using (var stream = new MemoryStream())
{
(new BinaryFormatter()).Serialize(stream, value);
stream.Flush();
return Convert.ToBase64String(stream.ToArray());
}
}
/// <summary>
/// Deserializes an object of type T from the string 'data'
/// </summary>
public static T DeserializeFromString<T>(string data)
{
byte[] bytes = Convert.FromBase64String(data);
using (var stream = new MemoryStream(bytes))
return (T)(new BinaryFormatter()).Deserialize(stream);
}
}
/// <summary>
/// A wrapper to serialize classes/structs that Unity can't
/// </summary>
public abstract class SerializedClass<T>
{
[SerializeField]
private string serializedData = string.Empty;
protected T _value;
public SerializedClass() { }
public SerializedClass(T value)
{
Value = value;
}
public bool IsNull { get { return this == null || Value == null; } }
public T Value
{
get
{
if (_value == null)
_value = Deserialize();
return _value;
}
set
{
if (_value == null || !_value.Equals(value))
{
_value = value;
Serialize();
}
}
}
public string Data { get { return serializedData; } }
public virtual void Serialize()
{
serializedData = _value == null ?
string.Empty : SerializationHelper.SerializeToString<T>(_value);
}
protected virtual T Deserialize()
{
return string.IsNullOrEmpty(serializedData) ?
default(T) : SerializationHelper.DeserializeFromString<T>(serializedData);
}
}
Then you could easily have serializable stuff, like System.Type
:
[Serializable]
public class SerializedType : SerializedClass<Type>
{
}
Dictionaries (not the best approach I know…):
[Serializable]
public class SerializedStringBoolDictionary : SerializedClass<Dictionary<string, bool>>
{
// it's important to remember not to create an empty constructor and new up a dictionary
// because unity will call that constructor upon deserialization and so you'll end up overwriting your data
public SerializedStringBoolDictionary(Dictionary<string, bool> dic)
: base(dic)
{
}
public bool TryGetValue(string key, out bool value)
{
return Value.TryGetValue(key, out value);
}
public bool this[string key]
{
get { return Value[key]; }
set
{
Value[key] = value;
Serialize();
}
}
}
Usage:
SerializedStringBoolDictionary dic = new SerializedStringBoolDictionary(Dictionary);
Now for serializing static state, I rarely require this but if I do, it’d be for Settings
objects which are usually ScriptableObjects
I usually follow this pattern:
[Serializable]
public class Settings : ScriptableObject
{
[SerializeField]
private float floatValue;
[SerializeField]
private string stringValue;
public float FloatValue { get { return floatValue; } }
public float StringValue { get { return stringValue; } }
public static float sFloatValue { get { return Instance.FloatValue; } }
public static string sStringValue { get { return Instance.StringValue; } }
private static Settings instance;
private static Settings Instance
{
get
{
return LazyLoadScriptableAsset(ref instance, settingsPath, true);
}
}
public static T LazyLoadScriptableAsset<T>(ref T value, string path, bool log) where T : ScriptableObject
{
if (value == null)
{
path = path.Replace('/', '\\'); // Normalize the path, could be the other way around too
value = AssetDatabase.LoadAssetAtPath(path, typeof(T)) as T;
if (value == null)
{
if (log) Debug.Log("No asset of type `" + typeof(T) + "` was found - creating new");
value = CreateScriptableObjectAsset<T>(path);
}
}
return value;
}
private static T CreateScriptableObjectAsset<T>(string path) where T : ScriptableObject
{
T so = CreateInstance<T>();
if (Path.GetExtension(path) != ".asset")
path += ".asset";
AssetDatabase.CreateAsset(so, path);
return so;
}
}
}
You know have a Settings
object that could have its value persist and serialize correctly with static access (if required)
I’m never a fan of static/global access, use this solution as a last resort/worse cast solution. Most the times you don’t need statics, why not just have normal Settings class that you could instantiate and pass around? Statics discourage code re-usability, encourage dependency hiding and makes your code harder to maintain.
As for InitializeOnLoad
It’s kind of vague I agree, instead of “Load”, I think it should be “AssemblyReload”