Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Add Generic instance in PropertyDrawer and serialize it

Discussion in 'Scripting' started by silentslack, May 12, 2023.

  1. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    Hi,

    I have a 'Model' class that holds a list of 'Variables':

    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class Model
    4. {
    5.     public List<Variable> Variables;
    6. }
    7.  
    8. [System.Serializable]
    9. public class Variable
    10. {
    11.     public string VariableName;
    12. }
    13.  
    14. [System.Serializable]
    15. public class Variable<T> : Variable
    16. {
    17.     public T VariableValue;
    18. }
    I want to be able to use the inspector to add a new variable of a concrete type. I want this to be handled in the PropertyDrawer of 'Model' so I'm using SerializedProperty. If I create a new instance of the target concrete type and try to set using 'BoxedValue' (Unity 2022+):


    Code (CSharp):
    1.  
    2. // Create the generic instance
    3. var typeInstance = typeof(Variable<>).MakeGenericType(new Type[] { valueType });
    4. var newVariable = (Variable)Activator.CreateInstance(typeInstance);
    5.  
    6. // Give it a name
    7. newVariable.VariableName = varName;
    8.  
    9. // Add it to the list
    10. var pVariables = model.FindPropertyRelative("Variables");
    11. var pNewVariable = pVariables.GetArrayElementAtIndex(pVariables.arraySize - 1);
    12. pNewVariable.boxedValue = newVariable;
    13.  
    I get the error:
    InvalidOperationException: The input to boxedValue has type 'Variable`1[[System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]', which does not match the expected type 'Variable'.

    Is there any way to handle this inside the drawer using SerializedProperty?

    Hoping some Unity serialization wizard comes to my rescue here!?

    Thanks! Jake
     
    Last edited: May 12, 2023
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Well
    Variable<T>
    doesn't inherit from
    Variable
    so that's not going to work to start with, unless that's just a typo in your example code.

    Also you need to be using polymorphic serialisation here, ergo using SerializeReference, and deal with managed reference values, such at this and similar.

    Nonetheless I do not believe Unity is capable of serialising an object with a generic parameter without an actual concrete type written out somewhere in code.
     
    Last edited: May 12, 2023
    Bunny83 likes this.
  3. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    @spiney199 Thanks for the quick response. That is a typo as I quickly wrote that up as a simple example to illusrtrate the issue - I'll edit that!

    Aah, I did not think of using SerializeReference here - thanks for the pointer, I'll check it out!
     
  4. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    Aaah, actually I did try to use serialize reference before but I get ther error:

    Fields with [SerializeReference] cannot serialize objects when the field type is a generic with supplied type parameters, in this case Variable<System.Boolean>
     
  5. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    The thing is Unity handles this case no problem in Unity 2022:

     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Yes that is a limitation of SerializeReference I think has only been made to be supported in 2023. So ultimately if you want polymorphism here without going to a super-beta Unity version,
    Variable<T>
    should be abstract, and you will need to make inherited concrete types such as
    BoolVariable : Variable<bool>
    if you plan to go down this route.

    I believe Odin serialisation should be able to handle this.
     
  7. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    @spiney199 it's actually what I have been doing up to now but really hoped there was a way so I didn't have to do this:


    Code (CSharp):
    1. [System.Serializable] public class Variable_Bool: Telex.Variable<System.Boolean> {}
    2.     [System.Serializable] public class PointerVariable_Bool: Telex.PointerVariable<System.Boolean> {}
    3.     [System.Serializable] public class Variable_Int: Telex.Variable<System.Int32> {}
    4.     [System.Serializable] public class PointerVariable_Int: Telex.PointerVariable<System.Int32> {}
    5.     [System.Serializable] public class Variable_Float: Telex.Variable<System.Single> {}
    6.     [System.Serializable] public class PointerVariable_Float: Telex.PointerVariable<System.Single> {}
    7.     [System.Serializable] public class Variable_String: Telex.Variable<System.String> {}
    8.     [System.Serializable] public class PointerVariable_String: Telex.PointerVariable<System.String> {}
    9.     [System.Serializable] public class Variable_Vector2: Telex.Variable<UnityEngine.Vector2> {}
    10.     [System.Serializable] public class PointerVariable_Vector2: Telex.PointerVariable<UnityEngine.Vector2> {}
    11.     [System.Serializable] public class Variable_Vector3: Telex.Variable<UnityEngine.Vector3> {}
    12.     [System.Serializable] public class PointerVariable_Vector3: Telex.PointerVariable<UnityEngine.Vector3> {}
    13.     [System.Serializable] public class Variable_object: Telex.Variable<System.Object> {}
    14.     [System.Serializable] public class PointerVariable_object: Telex.PointerVariable<System.Object> {}
    15.     [System.Serializable] public class Variable_Object: Telex.Variable<UnityEngine.Object> {}
    16.     [System.Serializable] public class PointerVariable_Object: Telex.PointerVariable<UnityEngine.Object> {}
    17.     [System.Serializable] public class Variable_Transform: Telex.Variable<UnityEngine.Transform> {}
    18.     [System.Serializable] public class PointerVariable_Transform: Telex.PointerVariable<UnityEngine.Transform> {}
    19.     [System.Serializable] public class Variable_Collider: Telex.Variable<UnityEngine.Collider> {}
    20.     [System.Serializable] public class PointerVariable_Collider: Telex.PointerVariable<UnityEngine.Collider> {}
    21.     [System.Serializable] public class Variable_LayerMask: Telex.Variable<UnityEngine.LayerMask> {}
    22.     [System.Serializable] public class PointerVariable_LayerMask: Telex.PointerVariable<UnityEngine.LayerMask> {}
    23.     [System.Serializable] public class Variable_CollisionDetectionMode: Telex.Variable<UnityEngine.CollisionDetectionMode> {}
    24.     [System.Serializable] public class PointerVariable_CollisionDetectionMode: Telex.PointerVariable<UnityEngine.CollisionDetectionMode> {}
    25.     [System.Serializable] public class Variable_RigidbodyInterpolation: Telex.Variable<UnityEngine.RigidbodyInterpolation> {}
    26.     [System.Serializable] public class PointerVariable_RigidbodyInterpolation: Telex.PointerVariable<UnityEngine.RigidbodyInterpolation> {}
    27.     [System.Serializable] public class Variable_JointLimits: Telex.Variable<UnityEngine.JointLimits> {}
    28.     [System.Serializable] public class PointerVariable_JointLimits: Telex.PointerVariable<UnityEngine.JointLimits> {}
    29.     [System.Serializable] public class Variable_AudioClip: Telex.Variable<UnityEngine.AudioClip> {}
    30.     [System.Serializable] public class PointerVariable_AudioClip: Telex.PointerVariable<UnityEngine.AudioClip> {}
    31.     [System.Serializable] public class Variable_AnimationClip: Telex.Variable<UnityEngine.AnimationClip> {}
    32.     [System.Serializable] public class PointerVariable_AnimationClip: Telex.PointerVariable<UnityEngine.AnimationClip> {}
    33.     [System.Serializable] public class Variable_TimelineAsset: Telex.Variable<UnityEngine.Timeline.TimelineAsset> {}
    34.     [System.Serializable] public class PointerVariable_TimelineAsset: Telex.PointerVariable<UnityEngine.Timeline.TimelineAsset> {}
    35.     [System.Serializable] public class Variable_StringList: Telex.Variable<System.Collections.Generic.List<string>> {}
    36.     [System.Serializable] public class Variable_Activity: Telex.Variable<SlackerApps.Activity> {}
    37.     [System.Serializable] public class PointerVariable_Activity: Telex.PointerVariable<SlackerApps.Activity> {}
    38.     [System.Serializable] public class PointerVariable_StringList: Telex.PointerVariable<System.Collections.Generic.List<string>> {}
    39.     [System.Serializable] public class Variable_Sprite : Telex.Variable<Sprite> { }
    40.     [System.Serializable] public class Variable_Collision : Telex.Variable<Collision> { }
    41.     [System.Serializable] public class Variable_Quaterion : Telex.Variable<Quaternion> { }
    42.     [System.Serializable] public class Variable_KeyCode : Telex.Variable<KeyCode> { }
    43.     [System.Serializable] public class Variable_Texture2D : Telex.Variable<Texture2D> { }
    :(
     
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Unfortunately that's just the limitation on Unity's serialisation in 2022 and prior. Even support for generics in 2023 is somewhat buggy from what I've seen.

    You can always try the Odin serialiser, which is open source. You only need to write a drawer to work with your Odin serialised types, I believe.
     
  9. silentslack

    silentslack

    Joined:
    Apr 5, 2013
    Posts:
    379
    Yeah no worries. For now, I'm ok with this solution but hope Unity 2023 can finally put these type of serialization issues to bed once and for all! Thanks for the help :)