Search Unity

How do you get a Generic/Template class to show in the inspector?

Discussion in 'Scripting' started by NeilM0, Jul 16, 2015.

  1. NeilM0

    NeilM0

    Joined:
    Mar 31, 2009
    Posts:
    135
    Does anyone know how to display a templated class in a monobehaviour? This only shows the list in the inspector, and not my custom TestTemp class.

    Code (CSharp):
    1.  
    2. public class TestTemplateMain : MonoBehaviour
    3. {
    4.    public TestTemp<int> mInts;// = new TestTemp<int>();
    5.    public List<int> mlist;
    6. }
    7.  
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. [System.Serializable]
    7. public class TestTemp<T>
    8. {
    9.    public T v;
    10.    public string s = "This isn't showing up";
    11. }
    12.  
     
  2. WheresMommy

    WheresMommy

    Joined:
    Oct 4, 2012
    Posts:
    890
    I'm not sure, but I think you gotta put that class in the first one to show all the things up.

    Code (CSharp):
    1. [Serializable]
    2.     public class Currency{
    3.         public float scoreSize ;
    4.         public string scoreName ;
    5.     }
    6.  
    7.     public Currency[] currencies ;
     
  3. NeilM0

    NeilM0

    Joined:
    Mar 31, 2009
    Posts:
    135
    That's no longer using generics.
     
  4. Timelog

    Timelog

    Joined:
    Nov 22, 2014
    Posts:
    528
    What 'asd' was more refering to was using a nested class, but that is not necessary to show up in the inspector.

    I personally think you can not see generic classes in the Inspector, due to the fact you need to give it it type when declaring it. Either that, or you need to add a constrain so it has a better idea what types it can support, or you need to be clear in your generic class whether 'T' it is a covariant, contravariant or both type.

    Isn't using a base non-generic class as a template an option for your case? That will work for sure.
     
  5. NeilM0

    NeilM0

    Joined:
    Mar 31, 2009
    Posts:
    135
    Hm, I'm not quite sure I understand. I'm confused as to what the terms covariant and contravariant mean. And with the non-generic class being an option - yes it's an option, I'm just unsure of how to do it.
     
  6. Timelog

    Timelog

    Joined:
    Nov 22, 2014
    Posts:
    528
    tantx likes this.
  7. NeilM0

    NeilM0

    Joined:
    Mar 31, 2009
    Posts:
    135
    Thanks. I guess I'm going to have to go about this another way. Back to the drawing board!
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    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:

    Code (csharp):
    1.  
    2. [System.Serializable()]
    3. public class FooClass
    4. {
    5.     public string ValueA;
    6. }
    7.  
    8. [System.Serializable()]
    9. public class BarClass : FooClass
    10. {
    11.  
    12.     public int ValueB;
    13.  
    14. }
    15.  
    16. public class MyScript : MonoBehaviour
    17. {
    18.  
    19.     public FooClass Obj = new BarClass();
    20.  
    21. }
    22.  
    In this example 2 things will happen.

    1) the inspector will only show the properties for FooClass (ValueA).
    2) 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:
    http://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html

    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/space...ter/SpacepuppyBase/Serialization/UnityData.cs

    Here you can see how you'd use it:
    Code (csharp):
    1.  
    2. using UnityEnginer;
    3. using System.Collections;
    4.  
    5. using com.spacepuppy.Serialization;
    6.  
    7. public class ExampleScript : MonoBehaviour, ISerializationCallbackReceiver
    8. {
    9.  
    10.     [System.NonSerialized()]
    11.     private GenericClass<Transform> _generic;
    12.     [SerializeField()]
    13.     private UnityData _data;
    14.  
    15.  
    16.     #region ISerializationNotificationCallback Interface
    17.  
    18.     public void OnAfterDeserialize()
    19.     {
    20.         _mover = SerializationHelper.BinaryDeserialize(_data) as GenericClass<Transform>;
    21.         _data.Clear();
    22.     }
    23.  
    24.     public void OnBeforeSerialize()
    25.     {
    26.         if (_data == null) _data = new UnityData();
    27.         SerializationHelper.BinarySerialize(_data, _generic);
    28.     }
    29.  
    30.     #endregion
    31.  
    32.  
    33.     [System.Serializable()]
    34.     public class GenericClass<T> where T : UnityEngine.Object;
    35.     {
    36.         public T SomeReferencedObject;
    37.         public string SomeStringData;
    38.     }
    39.  
    40. }
    41.  
    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:

    Code (csharp):
    1.  
    2.     public class MovementController : SPComponent, IIgnorableCollision, IForceReceiver, ISerializationCallbackReceiver
    3.     {
    4.  
    5.         #region Events
    6.  
    7.         public event System.EventHandler<MovementControllerHitEventArgs> MovementControllerHit;
    8.  
    9.         #endregion
    10.  
    11.         #region Fields
    12.  
    13.         [SerializeField()]
    14.         private bool _resetVelocityOnNoMove = false;
    15.  
    16.         [System.NonSerialized()]
    17.         private bool _bMoveCalled = false;
    18.  
    19.         [System.NonSerialized()]
    20.         private IGameObjectMover _mover; //this needs to be serialized, but can't be
    21.         [System.NonSerialized()]
    22.         private System.Action _updateCache;
    23.         [System.NonSerialized()]
    24.         private bool _bInUpdateSequence;
    25.  
    26.         [System.NonSerialized()]
    27.         private Vector3 _lastPos;
    28.         [System.NonSerialized()]
    29.         private Vector3 _lastVel;
    30.  
    31.         [SerializeField()]
    32.         private UnityData _moverData; //this stores the actual serialized data for '_mover'
    33.  
    34.         [System.NonSerialized()]
    35.         private IGameObjectMover _pausedMover;
    36.  
    37.         [System.NonSerialized()]
    38.         private Transform _transform;
    39.  
    40.         #endregion
    41.  
    42.         #region CONSTRUCTOR
    43.  
    44.         protected override void Awake()
    45.         {
    46.             base.Awake();
    47.             //this.ChangeMoverType(_moverType);
    48.  
    49.             _transform = this.GetComponent<Transform>();
    50.             _lastPos = _transform.position;
    51.             _mover.Reinit(this);
    52.         }
    53.  
    54.         protected virtual void OnSpawn()
    55.         {
    56.             _lastPos = this.transform.position;
    57.             _mover.Reinit(this);
    58.             if (this.enabled) _mover.OnEnable();
    59.         }
    60.  
    61.         protected override void OnStartOrEnable()
    62.         {
    63.             base.OnStartOrEnable();
    64.  
    65.             _lastVel = Vector3.zero;
    66.             if (_mover != null) _mover.OnEnable();
    67.         }
    68.  
    69.         protected override void OnDisable()
    70.         {
    71.             base.OnDisable();
    72.  
    73.             if (_mover != null) _mover.OnDisable();
    74.         }
    75.  
    76.         #endregion
    77.  
    78.         #region ISerializationNotificationCallback Interface
    79.  
    80.         public void OnAfterDeserialize()
    81.         {
    82.             _mover = SerializationHelper.BinaryDeserialize(_moverData) as IGameObjectMover;
    83.             _moverData.Clear();
    84.         }
    85.  
    86.         public void OnBeforeSerialize()
    87.         {
    88.             if (_moverData == null) _moverData = new UnityData();
    89.             SerializationHelper.BinarySerialize(_moverData, _mover);
    90.         }
    91.  
    92.         #endregion
    93.  
    94.    
    95.    
    96.    
    97.         //... a LOT more stuff
    98.    
    99.    
    100.    
    101.         #region Special Mover Types
    102.    
    103.         public interface IGameObjectMover : IIgnorableCollision, System.IDisposable
    104.         {
    105.             //... ommitted
    106.         }
    107.    
    108.         [System.Serializable()]
    109.         public class DirectRigidBodyMover : IGameObjectMover
    110.         {
    111.             //... ommitted
    112.         }
    113.    
    114.         [System.Serializable()]
    115.         public class CharacterControllerMover : IGameObjectMover
    116.         {
    117.             //... ommitted
    118.         }
    119.    
    120.         [System.Serializable()]
    121.         public class RagdollBodyMover : IGameObjectMover
    122.         {
    123.             //... ommitted
    124.         }
    125.    
    126.         //... more mover types that are IGameObjectMovers
    127.    
    128.         #endregion
    129.    
    130.     }
    131.  
     
    jadvrodrigues likes this.
  9. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    This is an Old Thread, but i stumbled upon it during my search, this a solution/workaround that might be sufficient in most cases
    http://answers.unity3d.com/questions/214300/serializable-class-using-generics.html

    = Create a subclass that has explicit type for each Type you need

    Code (CSharp):
    1. //instances of this class will not show up in inspector
    2. [System.Serializable]
    3. public abstract class MyGenericClass<T>
    4. {
    5.    public T genericVariable;
    6. }
    7.  
    8. //but explicitely typed subclasses will show up
    9. [System.Serializable]
    10. public class MyGenericClassInt: MyGenericClass<int>
    11. {
    12. }
    13.  
    14. //variable example:
    15. public MyGenericClassInt myVar; //this will show up in Inspector with 'genericVariable' as int
    EDIT: improved example for clarity
     
    Last edited: Mar 19, 2020
    yeyekopi, shazwar, wolilioo and 10 others like this.
  10. MGGDev

    MGGDev

    Joined:
    Nov 6, 2018
    Posts:
    27
    Had the same problem, but nothing worked for me.
    There is a way that worked in my case, which is to override ToString(). I had the following classes:

    Code (CSharp):
    1. public class MyScript : MonoBehavior
    2. {
    3. public BaseClass C; // no need to specify a type.
    4. public void ShowValue()
    5. {
    6. Debug.Log(C.ToString());
    7. }
    8. }
    Code (CSharp):
    1. public class ChildClass<T> : BaseClass
    2. {
    3. public T Data;
    4. public override string ToString()
    5. {
    6. // return base.ToString() // We are not interested in 'base'
    7. return Data.ToString();
    8. }
    9. }
    This way, BaseClass could show up in the inspector, and no need to specify a type, just drag and drop any variable inheriting BaseClass and it will show up in the inspector.

    My case was simple of course, complex data structures might require some work though..

    If anyone knows a better way that would be nice :)
     
    Last edited: Dec 9, 2018
  11. guneyozsan

    guneyozsan

    Joined:
    Feb 1, 2012
    Posts:
    99
    I use the same method. I suggest making the generic classes that derive from Monobehaviour
    abstract
    . This prevents a lot of trouble that can happen with Unity.
     
    Marrt likes this.