Search Unity

  1. Want to see 2020.1b in action? Sign up for our Beta 2020.1 Overview Webinar on April 20th for a live presentation from our evangelists and a Q&A session with guests from R&D.
    Dismiss Notice
  2. Interested in giving us feedback? Join our online research interviews on a broad range of topics and share your insights with us.
    Dismiss Notice
  3. We're hosting a webinar for the new Input System where you'll be able to get in touch with the devs. Sign up now and share your questions with us in preparation for the session on April 15.
    Dismiss Notice
  4. Dismiss Notice

SerializeReference Attribute?

Discussion in '2019.3 Beta' started by SonicBloomEric, May 15, 2019.

  1. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    1,909
    This issue should be fixed in 2019.3.0f1 and 2020.1.0a15.
     
    Catsoft-Studios likes this.
  2. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    I've sent a bug report with the reproduction project; case ID: 1204105
     
    phobos2077, Peter77 and LeonhardP like this.
  3. kyuskoj

    kyuskoj

    Joined:
    Aug 28, 2013
    Posts:
    31
    Hello. I'm really happy with using [SerializeReference] but I got an issue here.
    I receive this message when I use RegisterCompleteObjectUndo to change the managedReferenceValue value.
    Am I doing something wrong? I am using 2019.3.0f3.
    Code (CSharp):
    1. GenericMenu menu = new GenericMenu() { allowDuplicateNames = true };
    2. menu.AddItem(new GUIContent(path), valueType == genericArgType, () =>
    3. {
    4.     Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Change Type");
    5.     property.managedReferenceValue = VarBase.CreateInstance(t);
    6.     property.serializedObject.ApplyModifiedProperties();
    7. });
     
  4. Catsoft-Studios

    Catsoft-Studios

    Joined:
    Jan 15, 2011
    Posts:
    592
    Same here. It's a known issue and a few messages above they said they are working on it.
     
    NWHCoding likes this.
  5. alex-abreu-unity

    alex-abreu-unity

    Unity Technologies

    Joined:
    Jan 18, 2018
    Posts:
    5
    kyuskoj, larex39, phobos2077 and 2 others like this.
  6. kyuskoj

    kyuskoj

    Joined:
    Aug 28, 2013
    Posts:
    31
    Hi. Thanks for the quick response. :)
    I got an error message while applying an overridden prefab. (The variable has a SerializeReference attribute)
    Not only it is not applied, it also reverts to the original value when playing.
    Does [SerializeReference] support prefab now? (2019.3.0f3)

    ps. I found a thread. Is it still in the same situation? I really hope it supports the prefab fully.
     
    Last edited: Dec 17, 2019
    TextusGames and jasonatkaruna like this.
  7. jasonatkaruna

    jasonatkaruna

    Joined:
    Feb 26, 2019
    Posts:
    56

    Speak of the devil. I have been debugging for the past 3 hours trying to figure out why my serialize references were clearing on my prefab.

    Using a custom editor with this snippet of code:

    Code (CSharp):
    1. serializedObject.Update();
    2. _skeleton.managedReferenceValue = HumanSkeletonTree.FromAnimator(_humanSkeletonView.Animator);
    3. serializedObject.ApplyModifiedPropertiesWithoutUndo();
    I guess I'll try to generate it at runtime if the value is null until this issue is fixed. Working fine for scriptable objects however!
     
  8. jasonatkaruna

    jasonatkaruna

    Joined:
    Feb 26, 2019
    Posts:
    56
    I have this setup:


    Code (CSharp):
    1. [Serializable]
    2. public abstract class TreeNode<TNode> : ISerializationCallbackReceiver, IEnumerable<TNode>
    3.         where TNode : TreeNode<TNode>
    4. {
    5.         [SerializeReference]
    6.         private TNode _parent;
    7.  
    8.         [SerializeReference]
    9.         private List<TNode> _children;
    10.  
    11.         public TNode Parent
    12.         {
    13.             get => _parent;
    14.             set => _parent = value;
    15.         }
    16.  
    17.         public HashSet<TNode> Children { get; set; }
    18.  
    19.         public TNode AddChild(TNode child)
    20.         {
    21.             if (child == null)
    22.             {
    23.                 throw new ArgumentNullException(nameof(child));
    24.             }
    25.             if (Children == null)
    26.             {
    27.                 Children = new HashSet<TNode>();
    28.             }
    29.             if (!Children.Contains(child))
    30.             {
    31.                 Children.Add(child);
    32.                 child.Parent = this as TNode;
    33.             }
    34.             return this as TNode;
    35.         }
    36.  
    37.         public TNode RemoveChild(TNode child)
    38.         {
    39.             if (child == null)
    40.             {
    41.                 throw new ArgumentNullException(nameof(child));
    42.             }
    43.             if (Children != null && Children.Contains(child))
    44.             {
    45.                 Children.Remove(child);
    46.                 child.Parent = null;
    47.             }
    48.             return this as TNode;
    49.         }
    50.  
    51.         public void OnAfterDeserialize()
    52.         {
    53.             Children = _children?.ToHashSet();
    54.         }
    55.  
    56.         public void OnBeforeSerialize()
    57.         {
    58.             _children = Children?.ToList();
    59.         }
    60.  
    61.         public IEnumerator<TNode> GetEnumerator()
    62.         {
    63.             yield return this as TNode;
    64.             if (Children != null)
    65.             {
    66.                 foreach (var child in Children)
    67.                 {
    68.                     if (child != null)
    69.                     {
    70.                         foreach (var childChild in child)
    71.                         {
    72.                             yield return childChild;
    73.                         }
    74.                     }
    75.                 }
    76.             }
    77.         }
    78.  
    79.         IEnumerator IEnumerable.GetEnumerator()
    80.         {
    81.             return GetEnumerator();
    82.         }
    83. }
    84.  
    85. public class BoneNode : TreeNode<BoneNode>
    86. {
    87.     public HumanBodyBones bone;
    88. }
    89.  
    90. public class Test : ScriptableObject
    91. {
    92.     [SerializeReference]
    93.     public BoneNode rootBoneNode;
    94. }
    I'm running into this weird issue where when I close/reopen unity, all bone nodes under the root bone node disappear. I think it has something to do with the `ISerializationCallbackReceiver`.

    EDIT: Can kind of confirm it has to do with some weird behavior in `ISerializationCallbackReceiver`. I changed my Children to be a List and removed the ISerializationCallbackReceiver implementations and it works now...
     
    Last edited: Dec 18, 2019
  9. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    Ok, so I've got a response from the QA team and apparently this is by design: `object` is not planned to be supported any time soon. This is fine though, as I've already found a workaround.

    Though, I've stumbled upon other issue:

    When an item is serialized via [SerializeReference], its constructor and field initializers are not invoked when an instance of the item is created (deserialized) at runtime.

    Let's say we have the following serializable class:

    Code (CSharp):
    1. [System.Serializable]
    2. public class Item
    3. {
    4.     public string Value { get; set; } = "Default Value";
    5.  
    6.     public Item ()
    7.     {
    8.         Debug.Log("Item ctor invoked");
    9.     }
    10. }
    11.  
    When serialized without the [SerializeReference], the ctor is invoked and `Value` is assigned with the default value when the class is deserialized at runtime; however, when serialized by reference, neither ctor, not field initializer are invoked.

    I've sent a bug report (Case 1206352), but will greatly appreciate if anyone here could let me know if this is a bug or by design and won't be fixed (my current work is blocked by this).
     
  10. kyuskoj

    kyuskoj

    Joined:
    Aug 28, 2013
    Posts:
    31
    I don't know if this problem was intended but I solved this issue like this.
    Code (CSharp):
    1. public static object CreateInstance(Type t)
    2. {
    3.     var constructor = t.GetConstructors()[0];
    4.     var parameters = constructor.GetParameters().Select(p => p.HasDefaultValue ? p.DefaultValue : p.ParameterType.GetDefault()).ToArray();
    5.     var instance = constructor.Invoke(BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod | BindingFlags.CreateInstance,
    6.                                       null, parameters, System.Globalization.CultureInfo.InvariantCulture);
    7.     return instance;
    8. }
    btw, I'm stuck because of this problem. Are you ok with a prefab issue?
     
  11. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    Well, sure, it's possible to manually invoke the ctor via reflection, but I need that to happen when the object is initially created by Unity (when de-serialization happens). Otherwise, what's the point in serializing it in the first place? :)

    Regarding the prefab issue, don't think it'll affect me, as I have no plans to override types of the serialized fields.
     
  12. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    733
    I suspect it creates a new object in memory using a lower level equivalent of FormatterServices.GetUninitializedObject(Type). WCF does this too. In WCF this is generally worked around by doing any initialisation that is meant to happen by the constructor in a method marked with [OnDeserialize], something similar with ISerializationCallbackReceiver would probably work here.
     
    Elringus likes this.
  13. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    Yeah, I've also thought about this and was going to use ISerializationCallbackReceiver callbacks instead of ctors should the devs confirm that this behavior is by design. This will sadly make field initializers useless and complicate the stuff overall. Given the default Unity serialization invoke ctors and field initializers just fine, I really hope they'll be able to make SerializeReference work the same.
     
    phobos2077 likes this.
  14. Adrien-TA

    Adrien-TA

    Joined:
    Jan 11, 2019
    Posts:
    7
    Hi !

    I am currently working on several gameplay tools for a kinda complex rpg system, and took a few hours to test the new SerializeReference attribute.
    Polymorphism would be a great help for building data graphs, and references could potentially help when dealing with prefab overrides/variants when it comes to lists (overriding elements by reference, instead of by index).
    Unfortunately I ran into a lot of troubles and didn't manage to get something working smoothly, so here comes a few feedbacks and questions.

    So, I tried creating some script files in a fresh 2019.3 new project :
    - CommonBaseObject : stand-alone class, holding a single string.
    - TestScriptableObject : ScriptableObject, holding some CommonBaseObject data (with and without SerializeReference).
    - TestPrefabObject : MonoBehaviour, holding some CommonBaseObject data (with and without SerializeReference).

    Then I created some assets using those scripts to see what would happen.
    The first problems appear when trying to display or edit SerializeReference fields :
    - List<CommonBaseObject> is fully editable as expected.
    - [SerializeReference] List<CommonBaseObject> can grow or shrink, but contained elements are only visible if already instanciated. I can't instantiate or nullify an element.
    - [SerializeReference] CommonBaseObject fields have the same problem, if I don't instantiate them in code then I can do nothing with them.

    I assumed it means that polymorphism and references are only supported as behind-the-scene tools, and are not meant to be used in inspectors out of the box, so I tried creating some inspectors to manipulate them (add buttons to instantiate/nullify elements, display/edit their content).
    But I ran into a lot of troubles when trying to achieve that. I kinda managed to have what I wanted, but the drawer is not compatible for both SerializeReference fields and standard fields, and I get spammed by log errors on every operation.

    I also realized that references are serialized as a simple list, referenced by indexes recomputed every time something changes. I hoped using references would help build more robust editable objects when dealing with variants overrides (like having a variant overriding a precise element in a list, not an element at index, thus avoiding myriads of bugs when the parent list is modified), but it seems that this is not the case.


    So, my main question is, is this feature usable in 2019.3 for editor/tool/inspector purposes ?
    Is there a working example somewhere ?

    I tried looking around on the forums and didn't find much.
    Users @taylank and @HassanKhallouf had similar issues but didn't seem to get much answers.

    Having polymorphism would be a huge help when dealing with complex tools.
    It was a life saver on a lot of past projects when working with various engines, and I would love to use this again.
    We are considering building our own serialization structure using some json to get that, but it would be a huge time sink.


    Here is a little summary of the problems I found :


    >> Problem 1 : Error trying to manipulate the reference directly.


    Error : type is not a supported pptr value
    When trying to get property.objectReferenceValue in my PropertyDrawer.


    >> Problem 2 : Error trying to instantiate/reset the reference.

    Error : Generating diff of this object for undo because the type tree changed.
    When modifying the reference value this way :
    property.managedReferenceValue = new CommonBaseObject();
    Or this way :
    property.managedReferenceValue = null;

    using this before doesn't help :
    Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Test Undo");


    >> Problem 3 : CustomPropertyDrawer for object references not compatible with object values.

    Error : InvalidOperationException: Attempting to get the managed reference full typename on a SerializedProperty that is set to a 'CommonBaseObject'
    When trying to display listBaseObjects in the inspector, the CustomPropertyDrawer breaks since it is designed to handle references instead of objects.


    >> Problem 4 : CustomPropertyDrawer breaks if used in stand-alone, without a CustomEditor attached to the container object.

    Error : ArgumentException: Getting control 1's position in a group with only 1 controls when doing repaint
    When trying to display my TestScriptableObject in the inspector while its CustomEditor is commented out.


    >> Problem 5 : Overrides safety.


    References are stored as indexes, and prefab variants store their overrides by array index, so modifying the original array will totally break the variants (overrides will be applied on the wrong element).


    I will be away from the office next week, thanks in advance for any tips/help/answers !
     
  15. Adrien-TA

    Adrien-TA

    Joined:
    Jan 11, 2019
    Posts:
    7
    For reference, here is the code I used for the tests described above.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class CommonBaseObject
    7. {
    8.     public string name = "my name is Bob";
    9. }
    10.  
    11. [CreateAssetMenu(fileName = "TestScriptableObject", menuName = "ScriptableObjects/Test Scriptable Object")]
    12. public class TestScriptableObject : ScriptableObject
    13. {
    14.     public List<int> listIntegers = new List<int>();
    15.    
    16.     public List<CommonBaseObject> listBaseObjects = new List<CommonBaseObject>();
    17.    
    18.     [SerializeReference]
    19.     public List<CommonBaseObject> listBaseObjectReferences = new List<CommonBaseObject>();
    20.  
    21.     [SerializeReference]
    22.     CommonBaseObject baseObjectReferenceA;
    23.  
    24.     [SerializeReference]
    25.     CommonBaseObject baseObjectReferenceB = new CommonBaseObject();
    26. }
    27.  
    28. public class TestPrefabObject : MonoBehaviour
    29. {
    30.     public List<int> listIntegers = new List<int>();
    31.  
    32.     public List<CommonBaseObject> listBaseObjects = new List<CommonBaseObject>();
    33.    
    34.     [SerializeReference]
    35.     public List<CommonBaseObject> listBaseObjectReferences = new List<CommonBaseObject>();
    36. }
    37.  
    Code (CSharp):
    1.  
    2. [CustomPropertyDrawer(typeof(CommonBaseObject))]
    3. public class CommonBaseObjectEditor : PropertyDrawer
    4. {
    5.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    6.     {
    7.         EditorGUILayout.LabelField($">> CommonBaseObject Inspector for {label.text} <<");
    8.  
    9.         if (property.managedReferenceFullTypename == string.Empty)
    10.         {
    11.             EditorGUILayout.LabelField("object is null.");
    12.             GUILayout.BeginHorizontal();
    13.             {
    14.                 if (GUILayout.Button("Instanciate Base"))
    15.                 {
    16.                     Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Test Undo");
    17.                     property.managedReferenceValue = new CommonBaseObject();
    18.                 }
    19.  
    20.                 GUILayout.EndHorizontal();
    21.             }
    22.         }
    23.         else
    24.         {
    25.             SerializedProperty name = property.FindPropertyRelative("name");
    26.             EditorGUILayout.PropertyField(name);
    27.             if (GUILayout.Button("Reset"))
    28.             {
    29.                 Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Test Undo");
    30.                 property.managedReferenceValue = null;
    31.             }
    32.         }
    33.     }
    34. }
    35.  
    36. [CustomEditor(typeof(TestScriptableObject))]
    37. public class TestScriptableObjectEditor : Editor
    38. {
    39.     SerializedProperty m_listIntegers;
    40.     SerializedProperty m_listBaseObjects;
    41.     SerializedProperty m_listRefObjects;
    42.     SerializedProperty m_baseObjectReferenceA;
    43.     SerializedProperty m_baseObjectReferenceB;
    44.    
    45.     void OnEnable()
    46.     {
    47.         m_listIntegers = serializedObject.FindProperty("listIntegers");
    48.         m_listBaseObjects = serializedObject.FindProperty("listBaseObjects");
    49.         m_listRefObjects = serializedObject.FindProperty("listBaseObjectReferences");
    50.         m_baseObjectReferenceA = serializedObject.FindProperty("baseObjectReferenceA");
    51.         m_baseObjectReferenceB = serializedObject.FindProperty("baseObjectReferenceB");
    52.     }
    53.  
    54.     public override void OnInspectorGUI()
    55.     {
    56.         // Setup
    57.         serializedObject.Update();
    58.        
    59.         // Base lists
    60.         EditorGUILayout.PropertyField(m_listIntegers);
    61.         EditorGUILayout.PropertyField(m_listBaseObjects);
    62.  
    63.         // Ref List
    64.         EditorGUILayout.PropertyField(m_listRefObjects);
    65.  
    66.         // Object A
    67.         EditorGUILayout.Separator();
    68.         EditorGUILayout.LabelField("> ObjectReference A");
    69.         EditorGUILayout.PropertyField(m_baseObjectReferenceA);
    70.  
    71.         // Object B
    72.         EditorGUILayout.Separator();
    73.         EditorGUILayout.LabelField("> ObjectReference B");
    74.         EditorGUILayout.PropertyField(m_baseObjectReferenceB);
    75.  
    76.         // Finalize
    77.         serializedObject.ApplyModifiedProperties();
    78.     }
    79. }
    80.  
    81. [CustomEditor(typeof(TestPrefabObject))]
    82. public class TestPrefabObjectEditor : Editor
    83. {
    84.     SerializedProperty m_listIntegers;
    85.     SerializedProperty m_listBaseObjects;
    86.     SerializedProperty m_listRefObjects;
    87.    
    88.     void OnEnable()
    89.     {
    90.         m_listIntegers = serializedObject.FindProperty("listIntegers");
    91.         m_listBaseObjects = serializedObject.FindProperty("listBaseObjects");
    92.         m_listRefObjects = serializedObject.FindProperty("listBaseObjectReferences");
    93.     }
    94.  
    95.     public override void OnInspectorGUI()
    96.     {
    97.         // Setup
    98.         serializedObject.Update();
    99.        
    100.         // Base lists
    101.         EditorGUILayout.PropertyField(m_listIntegers);
    102.         EditorGUILayout.PropertyField(m_listBaseObjects);
    103.  
    104.         // Ref List
    105.         EditorGUILayout.PropertyField(m_listRefObjects);
    106.  
    107.         // Finalize
    108.         serializedObject.ApplyModifiedProperties();
    109.     }
    110. }
    111.  
     
  16. taylank

    taylank

    Joined:
    Nov 3, 2012
    Posts:
    145
    @Adrien-TA Given the state of Unity builds of late and the number of unfinished, unreliable features, I'd strongly advise against using SerializedReferences in production for at least a year. I personally don't have the time to do their QA for them anymore.
     
    chanon81, NWHCoding, Arkarit and 4 others like this.
  17. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    My issue about constructor and field initializers not invoked was closed as "by design": https://issuetracker.unity3d.com/product/unity/issues/guid/1206352

    Can someone from the devs team please clarify why it's by design? The default Unity serialization (as well as C# built-in one) invoke default class ctor, this behavior always was a standard. And in case it's by design, at the very least, there should be a way to customize the de-serialization pipeline, so we can invoke the constructors ourselves; is there something like this planned in the future?

    @alex-abreu-unity @LeonhardP @karl_jones @jasonm_unity3d @superpig
     
  18. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    According to the bug report `The constructor should be invoked at instantiation, at play-mode the instance is loaded in memory not re-instantiated.`

    It is possible to customize de-serialization by using ISerializationCallbackReceiver with your class.
     
  19. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    The ctor being invoked at instantiation once makes sense, but does this really happen? I mean, in the reproduction project I've sent with the bug report, a (non-serialized) field is assigned a value in ctor, but at play mode it doesn't has a value.

    ISerializationCallbackReceiver can't really replace constructors; imagine a custom class, where some mandatory initialization logic is inside constructor. Currently, the constructor is not invoked (at all) by Unity when it creates an instance of the class during de-serialization; at the same time, when we create instance from the managed code the constructor is invoked. To handle this, we have to put the same initialization logic in both class constructor and ISerializationCallbackReceiver callback, which is obviously a hack and degrades code quality.
     
  20. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    SonicBloomEric and Elringus like this.
  21. Elringus

    Elringus

    Joined:
    Oct 3, 2012
    Posts:
    502
    karl_jones likes this.
  22. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    12
    If you set the CustomPropertyDrawer argument useForChildren to true. Do you get the correct behavior in this case?
    We will be revisiting CustomPropertyDrawer interaction with
    https://issuetracker.unity3d.com/is...our-when-using-propertydrawer-with-interfaces
     
    karl_jones likes this.
  23. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    12
    We are currently working on a fix in the context of this issue:
    https://issuetracker.unity3d.com/is...zedobject-is-a-script-with-managed-references
     
    SugoiDev and karl_jones like this.
  24. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    It seems that there is a serious bug when using [SerializeReference] with custom PropertyDrawers.
    Say you have a:
    Code (CSharp):
    1. [SerializeReference] List<Module> modules = new List<Module>();
    where you store child modules such as MyModule1 and MyModule2. Each module has a different PropertyDrawer.
    What happens if you swap the positions of MyModule1 and MyModule2 in the list is that MyModule1 will be drawn using MyModule2's PropertyDrawer and vice versa.
    Another user had this problem and here is his detailed step-by-step writeup: https://qiita.com/enrike3/items/53d7b1ac98a6563de6ac

    My best guess is that PropertyDrawers for a list are getting cached in some way and when order changes the old property drawer is still used. Only thing that helps is clicking on another GameObject in the inspector so that the problematic inspector is not being drawn again and then going back to the problematic inspector - now everything is fine. Script reload also fixes it.

    A few screenshots:
    Modules list after adding two modules, all is good:
    upload_2020-1-13_11-16-16.png

    Modules list after deleting AerodynamicsModule, notice something weird?
    upload_2020-1-13_11-16-56.png

    Still present in 2019.3.0f4.

    ---

    Another related problem is when using ReorderableList with [SerializeReference] - it draws properly for the first repain but then as soon as another repaint is triggered (say by scrolling) this error happens (full log at the bottom of the post):
    InvalidOperationException: The operation is not possible when moved past all properties (Next returned false)
    This happens if any object stored in a SerializeReference list has a child property which is drawn using ReorderableList. E.g. UnityAction or even if the ReorderableList is drawn manually.
    Note that the first problem with wrong PropertyDrawer being used is also present in ReorderableList each time you try to drag an element or delete an element (except for the last element as this does not change element order). I can also confirm that all of the above works perfectly with non-SerializeReference list.

    Full error log for issue #2:
    Code (CSharp):
    1. InvalidOperationException: The operation is not possible when moved past all properties (Next returned false)
    2. UnityEditor.SerializedProperty.Verify (UnityEditor.SerializedProperty+VerifyFlags verifyFlags) (at <a9523235cc1343878f30105212177353>:0)
    3. UnityEditor.SerializedProperty.get_hasMultipleDifferentValues () (at <a9523235cc1343878f30105212177353>:0)
    4. UnityEditorInternal.ReorderableList.get_count () (at <a9523235cc1343878f30105212177353>:0)
    5. UnityEditorInternal.ReorderableList.GetListElementHeight () (at <a9523235cc1343878f30105212177353>:0)
    6. UnityEditorInternal.ReorderableList.GetHeight () (at <a9523235cc1343878f30105212177353>:0)
    7. UnityEditorInternal.UnityEventDrawer.GetPropertyHeight (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at <a9523235cc1343878f30105212177353>:0)
    8. UnityEditor.PropertyDrawer.GetPropertyHeightSafe (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at <a9523235cc1343878f30105212177353>:0)
    9. UnityEditor.PropertyHandler.GetHeight (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    10. UnityEditor.EditorGUI.GetPropertyHeightInternal (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    11. UnityEditor.EditorGUI.GetPropertyHeight (UnityEditor.SerializedProperty property, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    12. NWH.VehiclePhysics.VehicleDrawer.Field (System.String propertyRelativeName, System.String label, System.String tooltip, System.String unit, System.Boolean enabled, System.Boolean isObjectField, System.Boolean includeChildren, System.Int32 fieldHeight) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/VehicleDrawer.cs:360)
    13. NWH.VehiclePhysics.Modules.Trailer.TrailerHitchModuleDrawer.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Modules/Trailer/Editor/TrailerHitchModuleDrawer.cs:32)
    14. UnityEditor.PropertyDrawer.OnGUISafe (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at <a9523235cc1343878f30105212177353>:0)
    15. UnityEditor.PropertyHandler.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren, UnityEngine.Rect visibleArea) (at <a9523235cc1343878f30105212177353>:0)
    16. UnityEditor.PropertyHandler.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    17. UnityEditor.EditorGUI.PropertyFieldInternal (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    18. UnityEditor.EditorGUI.PropertyField (UnityEngine.Rect position, UnityEditor.SerializedProperty property, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    19. UnityEditor.EditorGUI.PropertyField (UnityEngine.Rect position, UnityEditor.SerializedProperty property) (at <a9523235cc1343878f30105212177353>:0)
    20. NWH.VehiclePhysics.VehicleDrawer.DrawCustomList (System.String propertyName) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/VehicleDrawer.cs:145)
    21. NWH.VehiclePhysics.Modules.ModuleManagerDrawer.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/ModuleManager/Editor/ModuleManagerDrawer.cs:95)
    22. UnityEditor.PropertyDrawer.OnGUISafe (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label) (at <a9523235cc1343878f30105212177353>:0)
    23. UnityEditor.PropertyHandler.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren, UnityEngine.Rect visibleArea) (at <a9523235cc1343878f30105212177353>:0)
    24. UnityEditor.PropertyHandler.OnGUI (UnityEngine.Rect position, UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren) (at <a9523235cc1343878f30105212177353>:0)
    25. UnityEditor.PropertyHandler.OnGUILayout (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren, UnityEngine.GUILayoutOption[] options) (at <a9523235cc1343878f30105212177353>:0)
    26. UnityEditor.EditorGUILayout.PropertyField (UnityEditor.SerializedProperty property, UnityEngine.GUIContent label, System.Boolean includeChildren, UnityEngine.GUILayoutOption[] options) (at <a9523235cc1343878f30105212177353>:0)
    27. UnityEditor.EditorGUILayout.PropertyField (UnityEditor.SerializedProperty property, System.Boolean includeChildren, UnityEngine.GUILayoutOption[] options) (at <a9523235cc1343878f30105212177353>:0)
    28. NWH.VehiclePhysics.EditorUtility.Draw (UnityEditor.SerializedProperty p, System.Boolean drawChildren, System.Boolean expanded) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/EditorUtility.cs:25)
    29. NWH.VehiclePhysics.EditorUtility.DrawProperty (UnityEditor.SerializedProperty p, System.Boolean drawChildren, System.Boolean expanded, System.Boolean disabled) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/EditorUtility.cs:52)
    30. NWH.VehiclePhysics.EditorUtility.DrawProperty (UnityEditor.SerializedObject obj, System.String propertyName, System.Boolean drawChildren, System.Boolean expanded) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/EditorUtility.cs:39)
    31. NWH.VehiclePhysics.VehicleControllerInspector.DrawModulesTab (NWH.VehiclePhysics.VehicleController vc) (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/VehicleControllerInspector.cs:228)
    32. NWH.VehiclePhysics.VehicleControllerInspector.OnInspectorGUI () (at Assets/NWH Vehicle Physics 2/Scripts/Vehicle/Editor/VehicleControllerInspector.cs:133)
    33. UnityEditor.UIElements.InspectorElement+<>c__DisplayClass55_0.<CreateIMGUIInspectorFromEditor>b__0 () (at <a9523235cc1343878f30105212177353>:0)
    34. UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)


    Also a short video of both bugs at the same time:
     
    Last edited: Jan 13, 2020
  25. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    Funny you should mention this, I just encountered this bug myself.
    I'm just looking into it but would you also be able to file a bug report?
     
    NWHCoding likes this.
  26. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    I did three weeks ago - but at that point I thought it was just related to ReorderableList but now I see that it also happens with just a basic 'iterate over properties in list and draw them' solution too. I tried many things but was unable to fix it.
    Ticket number 1207230.
     
    karl_jones and LeonhardP like this.
  27. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    12
    I believe this is a duplicate of : https://issuetracker.unity3d.com/is...-wrong-propertydrawers-after-array-reordering
    On which we are working currently.
     
  28. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
  29. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    12
    I believe the fix might address both issues at the same time. I will validate, otherwise we will look at the other issues afterwards.
     
    NWHCoding and karl_jones like this.
  30. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    Any chance this will get solved in 2019.3.0f5?
     
  31. Charles_Beauchemin

    Charles_Beauchemin

    Unity Technologies

    Joined:
    Jan 18, 2017
    Posts:
    381
    maybe in 2019.3.0f6
     
    NWHCoding and karl_jones like this.
  32. Ramojus

    Ramojus

    Unity Technologies

    Joined:
    Nov 18, 2019
    Posts:
    6
  33. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    Will there be any kind of UI or right mouse click menu option to change variable with base type to available child types in inspector?
     
  34. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    Check the link I posted a few posts back (the Japanese one). If you are using ReorderableList you can create simple context menu that allows you to add any available child type and add it to the list. I am using it for adding modules to my script (except it does not work to the 2 bugs mentioned above).

    Also, a bit unrelated but I noticed that serializedProperty.isExpanded does not memorize the value (i.e. does not get marked dirty) when changed. It will immediately reset to initial value each Repaint when used in custom PropertyDrawer.
    Another user seems to have the same issue: https://answers.unity.com/questions/1420119/changing-isexpanded-property-from-another-editor.html
     
    TextusGames likes this.
  35. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    Nothing is planned at the moment however I did attempt to prototype something along those lines and hit a few issues with some key API missing from the SerializedProperty class. Once we have been able to address those issues then we can look into it again.
     
    Griz likes this.
  36. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    It is important if you want to work with serializedReference field in editor. I hope you will provide that feature in future.
     
  37. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    rwetzold, Griz and Peter77 like this.
  38. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    Hello. What kind of api did you miss?
    I tried to make generic selecting menu and kind of succeded.
    Could you try my project, and say that do you think?
    It is about 150 lines in core.
     
  39. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    Of the top of my head the SerializedProperty can not:
    1. Copy a reference from 1 field to another. E.G so the reference can be shared between fields in the editor. It needs a way to get the reference or reference id.
    2. Clear a reference. Assigning null did not work.
    Doing an editor to instantiate new objects for the Field is fine and I have that working but without the ability to share those references its missing one of the key points of the SerialzieReference.
     
  40. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    This is what I had

    Code (csharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
    6. public class ManagedReferenceAttribute : PropertyAttribute
    7. {
    8.     public readonly Type[] createTypes;
    9.     public readonly Type baseClass;
    10.  
    11.     public bool CanAdd
    12.     {
    13.         get
    14.         {
    15.             return (createTypes != null && createTypes.Length > 0) || baseClass != null;
    16.         }
    17.     }
    18.  
    19.     public ManagedReferenceAttribute(Type[] createNewTypes)
    20.     {
    21.         createTypes = createNewTypes;
    22.     }
    23.  
    24.     public ManagedReferenceAttribute(Type baseClassForCreate)
    25.     {
    26.         baseClass = baseClassForCreate;
    27.     }
    28. }
    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. // TODO: How to copy a property?
    8. // TODO: We have no way to get the reference.
    9. // TODO: How to clear a reference? Assigning null does not work it throws errors.
    10. // TODO: calling SerializedProperty.type causes a crash.
    11.  
    12. [CustomPropertyDrawer(typeof(ManagedReferenceAttribute))]
    13. public class ManagedReferencePropertyDrawer : PropertyDrawer
    14. {
    15.    const float k_ButtonWidth = 70;
    16.  
    17.    struct DragAndDropData
    18.    {
    19.        public SerializedProperty property;
    20.        public ManagedReferencePropertyDrawer drawer;
    21.    }
    22.  
    23.    void StartDragAndDrop(Rect position, SerializedProperty property)
    24.    {
    25.        DragAndDrop.PrepareStartDrag();
    26.        DragAndDrop.SetGenericData(nameof(ManagedReferencePropertyDrawer), new DragAndDropData() { drawer = this, property = property });
    27.        DragAndDrop.StartDrag("Dragging managed reference");
    28.    }
    29.  
    30.    void UpdateDragAndDrop(Rect position, SerializedProperty property)
    31.    {
    32.        // TODO: Check if assignable to the type and the serialized object
    33.        DragAndDrop.visualMode = DragAndDropVisualMode.Link;
    34.    }
    35.  
    36.    void StopDragAndDrop(Rect position, SerializedProperty property)
    37.    {
    38.        DragAndDrop.AcceptDrag();
    39.        var dragAndDropData = (DragAndDropData)DragAndDrop.GetGenericData(nameof(ManagedReferencePropertyDrawer));
    40.  
    41.        // TODO: How do we get the reference to copy it across?
    42.        property.objectReferenceInstanceIDValue = dragAndDropData.property.objectReferenceInstanceIDValue;
    43.        //property.managedReferenceValue = Activator.CreateInstance(type);
    44.    }
    45.  
    46.    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    47.    {
    48.        if (Event.current.type == EventType.MouseDrag && position.Contains(Event.current.mousePosition))
    49.        {
    50.            StartDragAndDrop(position, property);
    51.            Event.current.Use();
    52.        }
    53.        if (Event.current.type == EventType.DragUpdated && position.Contains(Event.current.mousePosition))
    54.        {
    55.            UpdateDragAndDrop(position, property);
    56.        }
    57.  
    58.        if (Event.current.type == EventType.DragPerform && position.Contains(Event.current.mousePosition))
    59.        {
    60.            StopDragAndDrop(position, property);
    61.        }
    62.  
    63.        var fullPosition = position;
    64.        var refAttribute = (ManagedReferenceAttribute)attribute;
    65.  
    66.        if (refAttribute.CanAdd)
    67.        {
    68.            var buttonRect = new Rect(position.x + position.width - k_ButtonWidth, position.y, k_ButtonWidth, position.height);
    69.            position.xMax -= k_ButtonWidth + 2;
    70.            if (EditorGUI.DropdownButton(buttonRect, new GUIContent("New"), FocusType.Passive))
    71.            {
    72.                var menu = new GenericMenu();
    73.                var types = GetCreateTypes();
    74.  
    75.                menu.AddItem(new GUIContent("Clear"), false, () => CreateItem(property, null));
    76.  
    77.                foreach(var type in types)
    78.                {
    79.                    menu.AddItem(new GUIContent(type.Name), false, () => CreateItem(property, type));
    80.                }
    81.                menu.DropDown(buttonRect);
    82.            }
    83.        }
    84.  
    85.  
    86.        if (!string.IsNullOrEmpty(property.managedReferenceFullTypename))
    87.        {
    88.            EditorGUI.PropertyField(fullPosition, property, label, true);
    89.        }
    90.        else
    91.        {
    92.            EditorGUI.LabelField(position, label);
    93.        }
    94.    }
    95.  
    96.    void CreateItem(SerializedProperty property, Type type)
    97.    {
    98.        if (type != null)
    99.        {
    100.            var createdObject = Activator.CreateInstance(type);
    101.            property.managedReferenceValue = createdObject;
    102.            property.objectReferenceInstanceIDValue
    103.        }
    104.        else
    105.        {
    106.            // TODO: DOes not work.
    107.            property.managedReferenceValue = null;
    108.        }
    109.        property.serializedObject.ApplyModifiedProperties();
    110.    }
    111.  
    112.    Type[] GetCreateTypes()
    113.    {
    114.        var refAttribute = (ManagedReferenceAttribute)attribute;
    115.  
    116.        if (refAttribute.baseClass != null)
    117.        {
    118.            var availableTypes = new List<Type>();
    119.            foreach(var type in TypeCache.GetTypesDerivedFrom(refAttribute.baseClass))
    120.            {
    121.                if (!type.IsAbstract)
    122.                    availableTypes.Add(type);
    123.            }
    124.            return availableTypes.ToArray();
    125.        }
    126.  
    127.        return refAttribute.createTypes;
    128.    }
    129.  
    130.    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    131.    {
    132.        if (string.IsNullOrEmpty(property.managedReferenceFullTypename))
    133.            return base.GetPropertyHeight(property, label);
    134.        return EditorGUI.GetPropertyHeight(property, label, true);
    135.    }
    136. }
    137.  
    Some of the issues have now been fixed(crashing etc)
     
    TextusGames likes this.
  41. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    Thanks for clarifying.

    1. There seems to be no valid way to get managedReferenceValue from the property for now :(
    I hope this will be added in the future.

    2. Setting reference to null is working now.
     
    Catsoft-Studios and karl_jones like this.
  42. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    Well, I did tell you to check out the link a few posts back where this is already done which would have saved you that hole day but anyways here it is for those that are searching for it (and with ReorderableList at that):
    https://qiita.com/enrike3/items/53d7b1ac98a6563de6ac
     
  43. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    299
    Thank you.
    I did check it before and found it limited and not generic for me. But it motivated me to make my own:).
    Qiita solution (as I understand):
    • It is for the reorderable list only.
    • It is not generic. You need to specify a type.
    • You can not directly change the type of already added element
    • It can not set variable to null
    And my solution :
    • It is a property drawer for the field of any supported by [SerializeReference] type with the additional attribute [SerializeReferenceMenu/Button] ( or just a custom static void - property.ShowManagedSelectMenu) that is available anywhere in UI there this variable is drawn.
    • It automatically defines the base type of the variable and collects appropriate child classes.
    • Type can be changed by middle mouse click or by a button (or custom)
    • The same menu allows setting to null
     
    NWHCoding likes this.
  44. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    Unity 2019.3.0f6 is out and unfortunately the issues mentioned here still persist.
    Only the issue with nested reorderable lists seems to be solved.
    Reordering elements in a list will still make them use the wrong drawer.
    I am guessing the fix was moved to 0f7?
     

    Attached Files:

    Last edited: Jan 28, 2020
  45. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,769
    Have you filled a bug report for this issue? Do you have a number so I can check the status?
     
  46. MartinBarrette

    MartinBarrette

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    12
    I can confirm that unfortunately the fix for the drawer has missed this build and should be in the next one.
     
    TextusGames, karl_jones and NWHCoding like this.
  47. McManning

    McManning

    Joined:
    Oct 27, 2017
    Posts:
    1
    Under 2019.3.0f6, what would be the best way to gracefully handle renamed classes that were stored as references?

    In my test, I have a Graph ScriptableObject containing a list of AbstractNode instances.

    When a node class is renamed (FooNode -> NewFooNode), I get "Unknown managed type referenced: [Assembly-CSharp] .FooNode" on recompilation once - but this prevents anything in the list from deserializing correctly. And once any saves are made to the containing ScriptableObject - the entire list of nodes saves as invalid references, not just the missing node entry.

    For example, if I save out the following graph (one FooNode and two BarNodes):

    Code (YAML):
    1. nodes:
    2.   - id: 0
    3.   - id: 1
    4.   - id: 2
    5.   references:
    6.     version: 1
    7.     00000000:
    8.       type: {class: FooNode, ns: , asm: Assembly-CSharp}
    9.       data:
    10.         name: Foo
    11.         fooValue1: 0
    12.         fooValue2: 0
    13.     00000001:
    14.       type: {class: BarNode, ns: , asm: Assembly-CSharp}
    15.       data:
    16.         name: Bar
    17.         barValue1: 0
    18.         barValue2:
    19.     00000002:
    20.       type: {class: BarNode, ns: , asm: Assembly-CSharp}
    21.       data:
    22.         name: Bar
    23.         barValue1: 0
    24.         barValue2:

    And then rename FooNode to NewFooNode (first by renaming the file in Unity, then refactoring the class name in VS) and save the graph out again, I end up with:

    Code (YAML):
    1. nodes:
    2.   - id: 0
    3.   - id: 0
    4.   - id: 0
    5.  references:
    6.    version: 1
    7.    00000000:
    8.      type: {class: , ns: , asm: }

    Here's my test code for reference:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using System;
    6.  
    7. // In FooNode.cs
    8. public class FooNode : AbstractNode
    9. {
    10.     public int fooValue1;
    11.     public bool fooValue2;
    12.  
    13.     public FooNode() : base()
    14.     {
    15.         name = "Foo";
    16.     }
    17. }
    18.  
    19. // In BarNode.cs
    20. public class BarNode : AbstractNode
    21. {
    22.     public float barValue1;
    23.     public string barValue2;
    24.  
    25.     public BarNode() : base()
    26.     {
    27.         name = "Bar";
    28.     }
    29. }
    30.  
    31. // In AbstractNode.cs
    32. [Serializable]
    33. public abstract class AbstractNode
    34. {
    35.     public string name;
    36. }
    37.  
    38. // In Graph.cs
    39. [CreateAssetMenu(fileName = "New Test Graph", menuName = "Test Graph")]
    40. public class Graph : ScriptableObject
    41. {
    42.     public string displayName;
    43.  
    44.     [SerializeReference]
    45.     public List<AbstractNode> nodes = new List<AbstractNode>();
    46. }
    47.  
    48. [CustomEditor(typeof(Graph))]
    49. public class GraphInspector : Editor
    50. {
    51.     public override void OnInspectorGUI()
    52.     {
    53.         base.OnInspectorGUI();
    54.  
    55.         var graph = target as Graph;
    56.  
    57.         // Test adding/removing nodes and undo support
    58.         if (GUILayout.Button("Add Foo Node"))
    59.         {
    60.             Undo.RegisterCompleteObjectUndo(graph, "Add Foo Node");
    61.             graph.nodes.Add(new FooNode());
    62.             EditorUtility.SetDirty(graph);
    63.         }
    64.  
    65.         if (GUILayout.Button("Add Bar Node"))
    66.         {
    67.             Undo.RegisterCompleteObjectUndo(graph, "Add Bar Node");
    68.             graph.nodes.Add(new BarNode());
    69.             EditorUtility.SetDirty(graph);
    70.         }
    71.  
    72.         // Test accessors
    73.         var nodes = serializedObject.FindProperty("nodes");
    74.         for (int i = 0; i < nodes.arraySize; i++)
    75.         {
    76.             var node = nodes.GetArrayElementAtIndex(i);
    77.             Debug.Log($"Node {i}: Full Typename: {node.managedReferenceFullTypename}, Field Typename: {node.managedReferenceFieldTypename}");
    78.         }
    79.     }
    80. }
    81.  
    Edit: I made the following workaround for handling upgrades - it's not amazing and probably not performant on large data sets (since it abuses JSON serialization) - so I'm open to suggestions for improvement.

    Basically: I keep the old class name in the project as a stub and add a DeprecatedNode attribute with an upgrade path. The Graph ScriptableObject will then automatically rebuild the upgraded instances during OnBeforeSerialize.

    Code (CSharp):
    1. [AttributeUsage(AttributeTargets.Class)]
    2. public class DeprecatedNode : Attribute
    3. {
    4.     public Type upgrade;
    5. }
    6.  
    7. // Step 1: Rename FooNode to NewFooNode:
    8.  
    9. public class NewFooNode : AbstractNode
    10. {
    11.     public int fooValue1;
    12.     public bool fooValue2;
    13.  
    14.     public NewFooNode() : base()
    15.     {
    16.         name = "Foo";
    17.     }
    18. }
    19.  
    20. // Step 2: Add a stub class for the legacy name and tag it with the upgrade path:
    21.  
    22. [DeprecatedNode(upgrade = typeof(NewFooNode))]
    23. public class FooNode : NewFooNode { }
    24.  
    25. // Step 3: and then let the Graph SO handle the migration during serialization:
    26.  
    27. [CreateAssetMenu(fileName = "New Test Graph", menuName = "Test Graph")]
    28. public class Graph : ScriptableObject, ISerializationCallbackReceiver
    29. {
    30.     ...
    31.     public void OnBeforeSerialize()
    32.     {
    33.         for (int i = 0; i < nodes.Count; i++)
    34.         {
    35.             if (Attribute.GetCustomAttribute(nodes[i].GetType(), typeof(DeprecatedNode)) is DeprecatedNode deprecated)
    36.             {
    37.                 Debug.LogWarning($"{nodes[i].GetType().Name} is deprecated. Upgrading to {deprecated.upgrade.Name}.");
    38.            
    39.                 // Gross workaround that abuses JSON serialization for recasting
    40.                 var json = JsonUtility.ToJson(nodes[i]);
    41.                 nodes[i] = JsonUtility.FromJson(json, deprecated.upgrade) as AbstractNode;
    42.             }
    43.         }
    44.     }
    45. }
    I also noticed that during OnAfterDeserialize() - the SerializeReference node list just contains nulls. I would have thought that the references were repopulated prior to the deserialization event being called.
     
    Last edited: Jan 29, 2020
  48. Hyp-X

    Hyp-X

    Joined:
    Jun 24, 2015
    Posts:
    242
    I reported an issue: (Case 1215642) A scripted class has a different serialization layout when loading

    I haven't seen this reported in this forum yet.

    If I put a following test script in a scene, I get error messages when I enter play mode.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. namespace Game
    6. {
    7.     public interface ITestInterface
    8.     {
    9.  
    10.     }
    11.  
    12.     [Serializable]
    13.     public struct TestReferenceHolder
    14.     {
    15.         [SerializeReference]
    16.         public ITestInterface objective;
    17.     }
    18.  
    19.     [Serializable]
    20.     public class TestScriptState
    21.     {
    22.         public List<TestReferenceHolder> serverObjectiveStates = new List<TestReferenceHolder>();
    23.     }
    24.  
    25.     public class TestScript : MonoBehaviour
    26.     {
    27.         public TestScriptState state;
    28.     }
    29. }
    The messages are:

    Code (CSharp):
    1. A scripted class has a different serialization layout when loading. (Read 4 bytes but expected 52 bytes)
    2. Did you change any of your scripts when entering playmode?
    3. A scripted object (probably Game.TestScript?) has a different serialization layout when loading. (Read 84 bytes but expected 132 bytes)
    4. Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?
    5.  
     
    Lelepoop likes this.
  49. NWHCoding

    NWHCoding

    Joined:
    Jul 12, 2012
    Posts:
    959
    In my experience so far [SerializeReference] has been quite badly implemented and things such as renaming the serialized list or just removing the [SerializeReference] attribute will cause a lot of headache.
     
    Jes28 likes this.
  50. danBourquin

    danBourquin

    Joined:
    Nov 16, 2016
    Posts:
    14
    SerializeReference don't persist between scene changing on editor or unity reboot if the class don't have any field.
    Is it on purpose or is it a bug?
    Interface :
    Code (CSharp):
    1.     public interface IFeedback
    2.     {
    3.         void PlayFeedback(AbstractCondition condition, AbstractActionBehaviour action, AbstractActionData data);
    4.     }
    Monobehaviour :
    Code (CSharp):
    1.     public class FeedbackContainer : UnityEngine.MonoBehaviour
    2.     {
    3.         [SerializeReference]
    4.         public IFeedback Feedback;
    5.  
    6.         [ContextMenu("Add fake feedback")]
    7.         public void AddFakeFeedback()
    8.         {
    9.             Feedback = new FakeFeedback();
    10.             UnityEditor.EditorUtility.SetDirty(this);
    11.         }
    12.  
    13.         [ContextMenu("Check fake feedback")]
    14.         public void CheckFeedback()
    15.         {
    16.             Debug.Log($"Check Feedback : {Feedback != null}");
    17.         }
    18.     }
    And the FakeFeedback used for test:
    Code (CSharp):
    1.     public class FakeFeedback : IFeedback
    2.     {
    3.         public float TestField;
    4.         public void PlayFeedback(AbstractCondition condition, AbstractActionBehaviour action, AbstractActionData data)
    5.         {
    6.             throw new NotImplementedException();
    7.         }
    8.     }
    If the FakeFeedback script don't implement a field (Like "TestField") it wont be serialized on the FeebackContainer monobehaviour.

    The walkaround is to make an abstractFeedback integrating the interface and having a least a field. But before I apply this solution I would like to be sure this is not a bug.
     
    NWHCoding likes this.
unityunity