Unity does not directly support serializing generic types.
Also, I would like to point out that unity serializes based on the type that the field is, not the type of the object in the field. This means that if you use inheritance, and set the field type to the parent type, it will deserialize as that parent type.
example:
[System.Serializable()]
public class FooClass
{
public string ValueA;
}
[System.Serializable()]
public class BarClass : FooClass
{
public int ValueB;
}
public class MyScript : MonoBehaviour
{
public FooClass Obj = new BarClass();
}
In this example 2 things will happen.
- the inspector will only show the properties for FooClass (ValueA).
- even if you set the instance to BarClass, the next time it is deserialized, it’ll be turned into a FooClass.
There is a work around for all of this though. Both your generic issue, and this class inheritance issue.
Unity introduced the ISerializationCallbackReceiver:
With this you can serialize and deserialize anything you want using whatever serialization process you want. You can use .net’s binary serialization for instance.
Of course, you’ll need to ALSO create a custom inspector for the script as well, so that it appears in the inspector.
Another downside though is that these customly serialized classes don’t deal with unity Object references (references to gameobjects and components) very well. Unity’s built in serializer doesn’t serialize the reference and instead stores an ‘id’ value for the thing it references and attaches that reference after deserialization.
Following is how I do this personally.
I do this by creating a custom serializable data container that breaks apart serializable data from unity object references:
https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/Serialization/UnityData.cs
Here you can see how you’d use it:
using UnityEnginer;
using System.Collections;
using com.spacepuppy.Serialization;
public class ExampleScript : MonoBehaviour, ISerializationCallbackReceiver
{
[System.NonSerialized()]
private GenericClass<Transform> _generic;
[SerializeField()]
private UnityData _data;
#region ISerializationNotificationCallback Interface
public void OnAfterDeserialize()
{
_mover = SerializationHelper.BinaryDeserialize(_data) as GenericClass<Transform>;
_data.Clear();
}
public void OnBeforeSerialize()
{
if (_data == null) _data = new UnityData();
SerializationHelper.BinarySerialize(_data, _generic);
}
#endregion
[System.Serializable()]
public class GenericClass<T> where T : UnityEngine.Object;
{
public T SomeReferencedObject;
public string SomeStringData;
}
}
Here’s a snippet of a realworld complex class that I use it in. In it I have to serialize an object that is referenced as an interface:
public class MovementController : SPComponent, IIgnorableCollision, IForceReceiver, ISerializationCallbackReceiver
{
#region Events
public event System.EventHandler<MovementControllerHitEventArgs> MovementControllerHit;
#endregion
#region Fields
[SerializeField()]
private bool _resetVelocityOnNoMove = false;
[System.NonSerialized()]
private bool _bMoveCalled = false;
[System.NonSerialized()]
private IGameObjectMover _mover; //this needs to be serialized, but can't be
[System.NonSerialized()]
private System.Action _updateCache;
[System.NonSerialized()]
private bool _bInUpdateSequence;
[System.NonSerialized()]
private Vector3 _lastPos;
[System.NonSerialized()]
private Vector3 _lastVel;
[SerializeField()]
private UnityData _moverData; //this stores the actual serialized data for '_mover'
[System.NonSerialized()]
private IGameObjectMover _pausedMover;
[System.NonSerialized()]
private Transform _transform;
#endregion
#region CONSTRUCTOR
protected override void Awake()
{
base.Awake();
//this.ChangeMoverType(_moverType);
_transform = this.GetComponent<Transform>();
_lastPos = _transform.position;
_mover.Reinit(this);
}
protected virtual void OnSpawn()
{
_lastPos = this.transform.position;
_mover.Reinit(this);
if (this.enabled) _mover.OnEnable();
}
protected override void OnStartOrEnable()
{
base.OnStartOrEnable();
_lastVel = Vector3.zero;
if (_mover != null) _mover.OnEnable();
}
protected override void OnDisable()
{
base.OnDisable();
if (_mover != null) _mover.OnDisable();
}
#endregion
#region ISerializationNotificationCallback Interface
public void OnAfterDeserialize()
{
_mover = SerializationHelper.BinaryDeserialize(_moverData) as IGameObjectMover;
_moverData.Clear();
}
public void OnBeforeSerialize()
{
if (_moverData == null) _moverData = new UnityData();
SerializationHelper.BinarySerialize(_moverData, _mover);
}
#endregion
//... a LOT more stuff
#region Special Mover Types
public interface IGameObjectMover : IIgnorableCollision, System.IDisposable
{
//... ommitted
}
[System.Serializable()]
public class DirectRigidBodyMover : IGameObjectMover
{
//... ommitted
}
[System.Serializable()]
public class CharacterControllerMover : IGameObjectMover
{
//... ommitted
}
[System.Serializable()]
public class RagdollBodyMover : IGameObjectMover
{
//... ommitted
}
//... more mover types that are IGameObjectMovers
#endregion
}