Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

[SerializeReference] GenericSerializedReferenceInspectorUI

Discussion in '2020.1 Beta' started by TextusGames, Jan 21, 2020.

?

Do you want to have the ability to assign polymorphic classes right in inspector with default UI?

  1. Yes.

    407 vote(s)
    82.2%
  2. No.

    5 vote(s)
    1.0%
  3. Yes. You implementation is good.

    4 vote(s)
    0.8%
  4. Yes. Unity should adapt this implementation

    48 vote(s)
    9.7%
  5. Yes. Unity should make some different Ui

    31 vote(s)
    6.3%
  1. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Project now should be accessed from github page. GitHub repository :
    https://github.com/TextusGames/UnitySerializedReferenceUI

    I have made generic [SerializeReference] UI library which allows assigning serialized reference to any child type available in the whole project even in different assemblies (via TypeChache).

    There is 3 way you can use it
    1. Add [SerialzeReference] [SerialzeReferenceButton] on top of field
      and there button will be drawn that allows to select child types.
    2. Add [SerialzeReference] [SerialzeReferenceMenu] on top of field
      and you can press middle mouse button in order to assign child types
    3. Via custom property drawer. Use simple method to show automatic menu.
    View attachment 547053
    View attachment 547200

    Example of use
    Code (CSharp):
    1.     [SerializeReference]
    2.     [SerializeReferenceButton]
    3.     public List<AnimalBase> ButtonAnimals = new List<AnimalBase>();
    It proves that collecting all child types, making menu out of them, selecting and assigning it to serialized is possible.
    I hope unity will adopt this in the future or make something similar because it is possible and people need this UI functionality for [SerializeReference].

    upload_2020-5-16_20-45-20.png

    Edited. 02.13.20.
    Supports interfaces.
    Excludes types derived from UnityEngine.Object.
    Excluded abstract classes.
    Moved to package.

    Edited 02.28.20
    Major clean up and refactoring
    Core is now just 2 small files
    But everething else is bigget becouse it contains type restriction classes.

    Added Type restrictions (built in ones like include types, include interfaces, exclude types, exclude interfaces)
    Adde type substring restriction( But search is not imblemented in UI yet)
    Added ability to combine multiple restriction attributes.
    Added ability to write params of restricted types in new restriction attributes.
    Changed example.

    Edited 02.29.20
    GreatlySimplified type restriction code.
    There are now 2 built in atributes for restriction that can take both type or interface type as a parameter.
    Code (CSharp):
    1.     [Header("Field with multiple restrictions(Include, Exclude - Type, Interface")]
    2.     [SerializeReference]
    3.     [SerializeReferenceButton]
    4.     [SerializeReferenceUIRestrictionIncludeTypes(typeof(MammalBase), typeof(IFish))]
    5.     [SerializeReferenceUIRestrictionExcludeTypes(typeof(RedCat), typeof(IDog), typeof(BlackFish))]
    6.     public IAnimal AnimalWithRestriction = default;    
    Edited 05.16.20
    Project clean up.
    Github repository was created for this project.
    https://github.com/TextusGames/UnitySerializedReferenceUI
    Uploaded new package.
    But from now on new versions should be downloaded from github.

    Package is in attachement.
     

    Attached Files:

    Last edited: May 16, 2020
    Xaon, mitaywalle, Kirsche and 10 others like this.
  2. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Core class:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.  
    3. using System;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. public static class SerializedReferenceInspectorUI
    8. {
    9.     public static void ShowContextMenuForManagedReferenceOnMouseMiddleButton( this SerializedProperty property, Rect position )
    10.     {
    11.         var e = Event.current;
    12.         if (e.type != EventType.MouseDown || !position.Contains(e.mousePosition) || e.button != 2)
    13.             return;
    14.  
    15.         ShowContextMenuForManagedReference(property);
    16.     }
    17.  
    18.     /// Must be drawn before DefaultProperty in order to receive input
    19.     public static void DrawSelectionButtonForManagedReference(this SerializedProperty property, Rect position)
    20.     {
    21.         var backgroundColor = new Color(0f, 0.8f, 0.15f, 0.67f);
    22.      
    23.         var buttonPosition = position;
    24.         buttonPosition.x += EditorGUIUtility.labelWidth + 1 * EditorGUIUtility.standardVerticalSpacing;
    25.         buttonPosition.width = position.width - EditorGUIUtility.labelWidth - 1 * EditorGUIUtility.standardVerticalSpacing;
    26.         buttonPosition.height = EditorGUIUtility.singleLineHeight;
    27.  
    28.         var storedIndent = EditorGUI.indentLevel;
    29.         EditorGUI.indentLevel = 0;
    30.         var storedColor = GUI.backgroundColor;
    31.         GUI.backgroundColor = backgroundColor;
    32.      
    33.         var names = GetSplitNamesFromTypename(property.managedReferenceFullTypename);
    34.         var className = string.IsNullOrEmpty(names.ClassName) ? "Null (Assign)" : names.ClassName;
    35.         var assemblyName = names.AssemblyName;
    36.         if (GUI.Button(buttonPosition, new GUIContent(className, className + "  ( "+ assemblyName +" )" )))
    37.         {
    38.             property.ShowContextMenuForManagedReference();
    39.         }
    40.      
    41.         GUI.backgroundColor = storedColor;
    42.         EditorGUI.indentLevel = storedIndent;
    43.     }
    44.     public static void ShowContextMenuForManagedReference(this SerializedProperty property)
    45.     {
    46.         var context = new GenericMenu();
    47.         FillContextMenu();
    48.         context.ShowAsContext();
    49.  
    50.         void FillContextMenu()
    51.         {
    52.             context.AddItem(new GUIContent("Null"), false, MakeNull);
    53.             var realPropertyType = GetRealTypeFromTypename(property.managedReferenceFieldTypename);
    54.             if (realPropertyType == null)
    55.             {
    56.                 Debug.LogError("Can not get type from");
    57.                 return;
    58.             }
    59.  
    60.             var types = TypeCache.GetTypesDerivedFrom(realPropertyType);
    61.             foreach (var currentType in types)
    62.             {
    63.                 AddContextMenu(currentType);
    64.             }
    65.  
    66.             void MakeNull()
    67.             {
    68.                 property.serializedObject.Update();
    69.                 property.managedReferenceValue = null;
    70.                 property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // undo is bugged
    71.             }
    72.  
    73.             void AddContextMenu(Type type)
    74.             {
    75.                 var assemblyName =  type.Assembly.ToString().Split('(', ',')[0];
    76.                 var entryName = type + "  ( " + assemblyName + " )";
    77.                 context.AddItem(new GUIContent(entryName), false, AssignNewInstanceOfType, type);
    78.             }
    79.  
    80.             void AssignNewInstanceOfType(object typeAsObject)
    81.             {
    82.                 var type = (Type) typeAsObject;
    83.                 var instance = Activator.CreateInstance(type);
    84.                 property.serializedObject.Update();
    85.                 property.managedReferenceValue = instance;
    86.                 property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // undo is bugged
    87.             }
    88.         }
    89.     }
    90.  
    91.     public static Type GetRealTypeFromTypename(string stringType)
    92.     {
    93.         var names = GetSplitNamesFromTypename(stringType);
    94.         var realType = Type.GetType($"{names.ClassName}, {names.AssemblyName}");
    95.         return realType;
    96.     }
    97.     public static (string AssemblyName, string ClassName) GetSplitNamesFromTypename(string typename)
    98.     {
    99.         if (string.IsNullOrEmpty(typename))
    100.             return ("","");
    101.      
    102.         var typeSplitString = typename.Split(char.Parse(" "));
    103.         var typeClassName = typeSplitString[1];
    104.         var typeAssemblyName = typeSplitString[0];
    105.         return (typeAssemblyName,  typeClassName);
    106.     }
    107. }
    108.  
    109. #endif

    Attribute
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [AttributeUsage(AttributeTargets.Field)]
    5. public class SerializeReferenceButtonAttribute : PropertyAttribute
    6. {
    7.  
    8. }
    AttributeDrawer
    Code (CSharp):
    1. #if UNITY_EDITOR
    2.  
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. [CustomPropertyDrawer(typeof(SerializeReferenceButtonAttribute))]
    7. public class SerializeReferenceButtonAttributeDrawer : PropertyDrawer
    8. {
    9.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    10.     {
    11.         return EditorGUI.GetPropertyHeight(property, true);
    12.     }
    13.  
    14.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    15.     {
    16.         EditorGUI.BeginProperty(position, label, property);
    17.         property.DrawSelectionButtonForManagedReference(position);
    18.         EditorGUI.PropertyField(position, property, true);
    19.         EditorGUI.EndProperty();
    20.     }
    21. }
    22. #endif

    Usage
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class Test : MonoBehaviour
    5. {
    6.     [SerializeReference]
    7.     [SerializeReferenceButton]
    8.     public List<AnimalBase> ButtonAnimals = new List<AnimalBase>();
    9. }
     
    ModLunar likes this.
  3. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    I would like Unity to add a middle mouse click menu in order to initialize any field marked with [SerializeReference] with child classes. It will be invisible and useful at the same time. And it will not conflict with existing ui and right click menu.
    Later that menu could be populated with copy and paste methods.
    That is just one thing I think is needed in order to use serializeReference in the inspector.
     
  4. camel82106

    camel82106

    Joined:
    Jul 2, 2013
    Posts:
    304
    Great work, I was really surprised that they add feature like this without proper inspector UI.

    I will try it at home with scriptable objects. I'm waiting for polymorphic lists for ages. Now I'm doing some not so nice workarounds because I want to avoid 3rd party serialization.

    So this is last part of puzzle. Well and I'm not so sure if SerializeReference is realiable enough for production usage too..
     
    phobos2077 and TextusGames like this.
  5. camel82106

    camel82106

    Joined:
    Jul 2, 2013
    Posts:
    304
    I have tried it yesterday with scriptable objects and it was working without problems.

    I will try it today with heavier setup. But from fast look onto implementation I suppose that you doesn't interfere with actual serialization you just add property drawer on top of it.

    So actual core serialization may be working as without it.

    Thanks
    Peter
     
    TextusGames likes this.
  6. Miyavi

    Miyavi

    Joined:
    Mar 10, 2012
    Posts:
    58
    This seems great!

    Is there any way to make this work with interfaces too?
     
  7. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Do not tested this. This uses unity typechache api if it allows to get all types with interfaces than it could work.

    One think that will not work definetly is scriptable objects or monobehaviours with interface ( they just are not serialized by interface with serialized reference attribute).

    However it is possible to use serializable wrappers for this.
    Make some base serializable class that inherits interface you want and than make serializable field of that type in another place.
    Than you can create childs of that class and inside of them you can add property or field of your existing objects with interface. You can even add scriptable object filed and then return data of that object casted to your interface.

    And serializable reference should be automatically populated with such wrapper types you create.
     
  8. valentingurkov

    valentingurkov

    Joined:
    Jun 17, 2019
    Posts:
    13
    I recently started reworking my hobbyist to use scriptable objects and somewhere in that system, I have different goals that implement an IGoal interface. With your work, I can now select from these goals nicely in the editor, something I was wondering how to do. Thank you for sharing this!
     
    TextusGames likes this.
  9. PassivePicasso

    PassivePicasso

    Joined:
    Sep 17, 2012
    Posts:
    100
    You can definitely serialize Interface assignments with SerializeReference. I do this in my VisualTemplates examples where I have a class, named EntityModel, which has an array IComponentData and another ISharedComponentData
    From this I have some UI to add elements to those array using a type ahead search.

    The serialization works fine in these cases, and the types and data are properly preserved through typical scenarios such as Copy/Pasta and save/close/load scene.
     
    TextusGames likes this.
  10. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    @Miyavi

    I updated the package to work with Interfaces.
    Now it skips classes derived from UnityEngine.Object(because serializeReferecne does not work with UnityEngine.Object) and abstract classes(because they should not be instantiated).
     
    Miyavi and valentingurkov like this.
  11. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
    I forwarded the thread to the team.
     
  12. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @TextusGames This is very awesome, thank you very much for sharing!

    I tried to build a boolean expression tree, creating a data structure that allows to combine boolean values with boolean operators:

    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3.  
    4. using UnityEngine;
    5.  
    6. public class Test : MonoBehaviour
    7. {
    8.     [SerializeReference]
    9.     [SerializeReferenceButton]
    10.     private BNode root;
    11. }
    12.  
    13. public interface IValueProvider<T>
    14. {
    15.     T Value { get; }
    16. }
    17.  
    18. [Serializable]
    19. public abstract class BNode : IValueProvider<bool>
    20. {
    21.     public abstract bool Value { get; }
    22. }
    23.  
    24. [Serializable]
    25. public class ValueNode : BNode {
    26.     [SerializeField]
    27.     private bool value = false;
    28.  
    29.     public override bool Value => value;
    30. }
    31.  
    32. [Serializable]
    33. public class AndNode : BNode
    34. {
    35.     [SerializeField]
    36.     private BNode[] children = default;
    37.  
    38.     public override bool Value => children.All(child => child.Value);
    39. }
    40.  
    41. [Serializable]
    42. public class OrNode : BNode
    43. {
    44.     [SerializeField]
    45.     private BNode[] children = default;
    46.  
    47.     public override bool Value => children.Any(child => child.Value);
    48. }
    However, in the Unity inspector, the array field
    children
    when selecting the types
    AndNode
    or
    OrNode
    in the attribute dropdown are not drawn. Do you happen to know what is going on?

    EDIT: Never mind, I am stupid. Using
    [SerializeField]
    is the culprit. Changing it to
    [SerializeReference][SerializeReferenceButton]
    for the array field
    children
    in both
    AndNode
    and
    OrNode
    works.

    EDIT2: However, the following steps crash my editor every time:
    1. In the inspector for a
      Test
      component, select the type
      AndNode
      from the dropdown of your type selection button for the field
      Root
      .
    2. Expand the foldout
      Root
      , then expand the foldout
      Children
      .
    3. Set the value of
      Size
      to
      2
      .
    4. Now it shows two buttons with
      AndNode
      as selected type in the
      Children
      foldout, below the
      Size
      property field.
    5. Open the dropdown of your type selection button for either of them and select
      ValueNode
      as type.
    6. Unity freezes for a moment and then crashes, closing itself and opening the "Report bug..." dialog.
    Interestingly, after setting the array size to any value greater zero in step 3 above, the default type of the object referenced there is
    AndNode
    and not
    Null
    , as I would expect. When selecting
    OrNode
    , it selects
    OrNode
    as default type for the children, not
    Null
    .

    At first I thought that using
    default
    as value for the array
    children
    was the cause, but even when setting it to null instead with
    private BNode[] children = null;
    , it still displays the behavior as described above.

    Is there a way to force
    Null
    and enable selecting the type for nested elements?

    EDIT3: After reading:
    I changed my code to this, removing the abstract base class:
    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3.  
    4. using UnityEngine;
    5.  
    6. public class Test : MonoBehaviour
    7. {
    8.     [SerializeReference]
    9.     [SerializeReferenceButton]
    10.     private IBooleanProvider root;
    11. }
    12.  
    13. public interface IValueProvider<T>
    14. {
    15.     T Value { get; }
    16. }
    17.  
    18. public interface IBooleanProvider : IValueProvider<bool> {}
    19.  
    20. [Serializable]
    21. public class ValueNode : IBooleanProvider
    22. {
    23.     [SerializeField]
    24.     private bool value = false;
    25.  
    26.     public bool Value => value;
    27. }
    28.  
    29. [Serializable]
    30. public class AndNode : IBooleanProvider
    31. {
    32.     [SerializeReference]
    33.     [SerializeReferenceButton]
    34.     private IBooleanProvider[] children = null;
    35.  
    36.     public bool Value => children.All(child => child.Value);
    37. }
    38.  
    39. [Serializable]
    40. public class OrNode : IBooleanProvider
    41. {
    42.     [SerializeReference]
    43.     [SerializeReferenceButton]
    44.     private IBooleanProvider[] children = null;
    45.  
    46.     public bool Value => children.Any(child => child.Value);
    47. }
    However, it does not change anything about the problem described in EDIT2. The problem might be related to nesting, since I serialize a class that contains at least one field that is also serialized using the attribute.

    EDIT4: To confirm that nested elements with [SerializeReference] are supported, I wrote this:

    Code (CSharp):
    1. [ExecuteInEditMode]
    2. public class Test : MonoBehaviour
    3. {
    4.     [SerializeReference]
    5.     private IBooleanProvider root;
    6.  
    7.     void OnEnable()
    8.     {
    9.         root = new AndNode();
    10.         (root as AndNode).children = new []{ new ValueNode(), new ValueNode() };
    11.     }
    12. }
    Unity does not crash at this and does show the right property fields, so presumably something goes wrong when using SerializeReferenceButtonAttribute?
     
    Last edited: Feb 20, 2020
  13. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429

    You are making a recursion.
    It is possibly crashing unity.
     
  14. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    This could explain the crashing, however, it shouldn't be making a recursion.

    • If I select
      AndNode
      as my type, its field
      children
      should be null or empty because its
      Size
      is zero.
    • If I then add elements to the array by increasing its size in the inspector, something already chooses for me that every new child element is an
      AndNode
      . Every array element should be
      Null
      . Same happens if I choose
      OrNode
      in my first step. Then adding new elements to the array makes them
      OrNode
      . Note that this, although being a recursion, does not crash Unity because Unity has some kind of recursion depth, if I recall correctly.
    • However, if I now try to change the type of an element in my array of
      AndNode
      or
      OrNode
      by using your dropdown from the
      [SerializeReferenceButton]
      , then Unity crashes.
     
  15. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    As a little addendum, it looks as if the serialized nested elements themselves are repeated. If you look on the screenshot that I attached, then the three nested elements from (A) are created by using the method I used in EDIT4 a few posts earlier, manually doing it in
    OnEnable()
    .

    If I now expand the foldout
    Children
    the last array element, the
    OrNode
    in (A), then it repeats the pattern, as seen in (B). This happens without me ever touching the dropdown created by
    SerializeReferenceButton
    .

    I can do this again a third time (not visible in the screenshot), but the inspector eventually appears to give up, refusing to expand the foldout
    Element0
    of such an element.
     

    Attached Files:

  16. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Unity has a serialisation depth limit equal to 7. It is possible that it is not properly implemented in serializeReference yet.
    In general [SeralizeReference] is still buggy. May be you need to report a bug to unity about a crash, and they will investigate it. because crashes should never happen.
     
  17. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    That looks to be the case, thanks for clarifying!

    Well, I hoped that maybe you know what is going on, because the crash does appear to come from using
    [SerializeReferenceButton]
    in combination with
    [SerializeReference]
    . In EDIT4 of my previous post, I confirmed that Unity does support using nested
    [SerializeReference]
    fields or in other words, fields for types who themselves contain fields with
    [SerializeReference]
    . However, using
    SerializeReferenceButton
    something goes havoc when trying to do it. Since your code presumably creates an instance of the selected type for the property, I hoped that you might have an idea why it selects the wrong type, not defaulting to null but creating this strange repeating pattern.

    Whenever I use the Reset option from the
    Test
    component context menu, I also get this error:
    Maybe it is related.

    I used the report feature after Unity crashes to submit the crash, linking here to the discussion. I hope that this is going to be resolved soon, as the combination of
    [SerializeReference]
    and a solution to select the type is essential for my project. If
    [SerializeReference]
    is ultimately supported in Unity and working, it is going to be a huge game changer. The main thread about the attribute highlights a lot of the fantastic thing that one can do with it.
     
    TextusGames likes this.
  18. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    After digging around a bit, I documented an interesting scenario that might provide insight into what is going on. Boiled done, it seems Unity does not like your
    Activator.CreateInstance(type)
    that you use for creating an instance, @TextusGames. As the official documentation by Microsoft states, it calls the default
    ctor()
    . Adding such a constructor to my classes, I revealed an interesting detail. The constructor looks like this, for instance for
    AndNode
    :
    Code (CSharp):
    1.     [SerializeReference]
    2.     [SerializeReferenceButton]
    3.     private IBooleanProvider[] children;
    4.  
    5.     public AndNode()
    6.     {
    7.         children = new IBooleanProvider[] { null };
    8.     }
    This is actually a working workaround. Well, it kind of is, but later more. Using constructors like this, Unity does not crash anymore when changing the type and thus the serialized object using
    [SerializedReferenceButton]
    and it adds a new element to the array being
    Null
    , as expected (Attachment 1).

    component_serializereferencebutton_attachment1.png

    Increasing the array size of such a serialized object now works fine. As expected, Unity creates a clone of the serialized object of the previously last element in the array (Attachment 2). Also as expected, these are actual true references. Which means, if I tick the
    Value
    property of
    Element0
    , the
    Value
    property of
    Element1
    is ticked, too. This is still expected, I guess? Though kinda annoying, but a different issue after all (or is it?).

    component_serializereferencebutton_attachment2.png

    The interesting stuff happens when you decrease the array size to zero and then increase it again, looking into the inspector. Taking the setup from Attachment 3 as base, set the
    Size
    of
    Children
    to zero. If you now increase it to one again, it is no longer
    Null
    , as we would expect with any array/list that has the
    [SerializeField]
    attribute, but it is a reference to the very first property of the nested structure (?).

    See Attachment 3 at the bottom of this post. This is a tree and I made sure that every element in there is "unique", which means there is no object in there that appears elsewhere, too. What I do now is to set the
    Size
    of
    Root.Children[1].Children
    to zero, then to one again. Now look at Attachment 4.
    Root.Children[1].Children[0]
    now references all other elements starting with
    Root
    , including itself
    , which means we have a recursion, explaining we we can expand the foldouts as long as the inspector allows it.

    component_serializereferencebutton_attachment4.png

    To come back to the original issue, for whatever reason, when Unity serializes an array with size greater zero, it takes the reference to the very first instance of the nested structure, in this case
    Root
    . It should become
    Null
    , but for whatever weird reason it does not. This means, if I change the type from the dropdown of
    [SerializeReferenceButton]
    anywhere in this nested structure, I actually try to change the type of all of the references and Unity crashes.

    To conclude, the bug appears to be located around the array serialization, when its size becomes greater zero for the first time, with Unity not making the new element
    Null
    in this particular case, but a reference to the property that the child property with
    [SerializeReference]
    is located in.

    I created a strongly simplified example script that hightlights this problem:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class Test2 : MonoBehaviour
    6. {
    7.     [SerializeReference]
    8.     private MyClass[] children;
    9.  
    10.     private void OnEnable()
    11.     {
    12.         children = new MyClass[] { new MyClass() };
    13.     }
    14. }
    15.  
    16. [Serializable]
    17. public class MyClass
    18. {
    19.     [SerializeReference]
    20.     MyClass[] children;
    21.  
    22.     [SerializeField]
    23.     private int changeThisNumber;
    24.  
    25.     public MyClass()
    26.     {
    27.         children = new MyClass[] { null };
    28.         changeThisNumber = 0;
    29.     }
    30. }
    Try changing the array size from one to zero to any number, then foldout, foldout and foldout, then change the value for changeThisNumber anywhere. Though remember that the problem is what Unity uses as element when changing the array size from zero to one and not that all these elements are the same instance.
     

    Attached Files:

    Last edited: Feb 20, 2020
  19. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    In edit4 you creating instance from code and assign it to an instance of the object. And after that the whole monobehaviour is serialized.

    SerializeReferenceButtonAttribute uses PropertyDrawer and is working with serializedProperty.ManagedReference.
    May be then setting serializedProperty.ManagedReference = newIClassInstance inside a recursively drawing SerializeReference fields, crash can happen.

    Again I do not think that recursive serialisation is officially allowed in unity.

    Without recursiveness SerializedPropertyButton is working great.
     
  20. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Report this example project as a bug to unity. Then let us know that they will say.

    Thank you for your efforts to make serialize reference a better thing. I personally think that this new serialize feature with inspector UI will be a game changer and must be Finalized and polished.
     
    hawaiian_lasagne likes this.
  21. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    There is likely a reason why the official documentation for
    [SerializeReference]
    says:
    Since the data structure I created is technically a tree and thus a graph, it is not officially supported to use recursion, I guess.

    Sure, but I genuinely don't understand why Unity would, when an array has no elements and needs one because its size increased to at least one element, take the reference to itself to fill this element, when the array is of the same type as the class and has a field with the
    [SerializeReference]
    attribute. In the simpliest of cases, this is exactly what appears to happen. Why not
    Null
    , as with virtually any other list and array that we use in Unity.

    Thanks, I already submitted one, but now I believe to be able to pinpoint the very issue and I don't know if I can still add this to the issue that I already submitted, got to look into it. Else, I agree with you. The ideas and improvements that one can do with
    [SerializeReference]
    , be it using it recursively or not, are incredible. Once it works properly, of course.

    EDIT: Looks like there is no way to change an already submitted report. I don't know what I should do now, I don't feel comfortable with opening another new one.
     
    Last edited: Feb 20, 2020
  22. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    If you have got a reply from unity you can send addition information to them.
     
  23. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Thank you, having received one, you are right and it even says at the bottom to reply if more information is available, did that now, let's see what they say.
     
    TextusGames likes this.
  24. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
    Ardenian and TextusGames like this.
  25. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Thanks for the heads up! I am excited to see that there is already a fix in review for version 2019.3 of Unity.
     
  26. DoctorShinobi

    DoctorShinobi

    Joined:
    Oct 5, 2012
    Posts:
    219
    Your package can really improve the usability of SerializeReference properties. Being able to select the implementation through the inspector is something I've wanted for a while now.
    Your implementation is almost perfect, and the only thing I'd add is a search bar(for scalability purposes).
     
    NotaNaN and TextusGames like this.
  27. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    It is totally possible.
    I just do not want to spend time on it now. Because SerializedReference is still too buggy and immature.
    Also, I feel like all unity default menus should provide the ability to search.
    But in the future, I can add it if unity will not make default UI itself and feature mature.
     
    DoctorShinobi likes this.
  28. PassivePicasso

    PassivePicasso

    Joined:
    Sep 17, 2012
    Posts:
    100
  29. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    My knowledge regarding
    SerializedProperty
    is little, can you reference another attribute of the same field? In the code by TextusGames, there is this part:

    Code (CSharp):
    1.             var realPropertyType = GetRealTypeFromTypename(property.managedReferenceFieldTypename);
    2.             if (realPropertyType == null)
    3.             {
    4.                 Debug.LogError("Can not get type from");
    5.                 return;
    6.             }
    7.  
    8.             var types = TypeCache.GetTypesDerivedFrom(realPropertyType);
    9.  
    It could be something like a
    [SerializeReferenceButtonRestriction(Type t)]
    which allows you to restrict a new base type. So if the field has type
    IList
    , for instance, you could add this attribute, giving a type deriving from
    IList
    and then you only get types in the dropdown that are or derive from the type defined in
    [SerializeReferenceButtonRestriction(Type t)]
    . Got to replace
    realPropertyType
    to the type stored in the attribute, if the attribute is present. I would write it myself, but I don't have the knowledge about SerializedProperty. If I have some time later on, I study ClassTypeReference, I know they did something with multiple attributes, might providing me with the knowledge that I need for writing the type restriction for
    [SerializeReferenceButton]
    .
     
  30. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Why do you need additional type restriction?

    Then you define field you already define it by base type or by interface. And only children of that base type or interface implementators will show up.

    About list and array.
    SerializeReference attribute is working with the element of a collection not with whole collection. So I can get and work with the type of element of collection not with the type of collection.

    Give a proper example of what you need and why( about additional restriction attribute)?
     
    Last edited: Feb 26, 2020
  31. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    @Ardenian
    If you was talking just about ui filtering. Than it should be implemented in ui.
    Lets say I have some searchString. Then I will just filter out type names that contains that substring(searchString) and add menu elements only for them.
    But I need to migrate from context menu to new editor window and drow searchbar and list of appropriate elements there.
    It however seems very doable. But a little bit later if needed.
     
  32. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Not a very common case, I guess. Let's say you make the field a general interface type like IList, but you do want to restrict which types implementing it. Not using an explicit type implementing this interface would allow one to keep operating over a user interface without the need to introduce an interface type from the base class.

    If you look on the link I posted, they have stuff like ClassImplements and ClassExtends as additional restrictions. Not really something that one needs right out of the box, but it could have its uses under certain circumstances. I could imagine in cases when one uses very general interfaces like IList, but still want to restrict the type selection to a very specific subset of types of all available types implementing the interface. Nonetheless, one could solve this by using an explicit type or introducing a new interface type for the needed type subset. In the end, it might actually be a lot more usable and maintainable.
     
  33. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    If you want to filter out by type of generic parameter of generic interface then I guess this is out of scope(too specific).

    I can, however add a list of bool delegates that takes a Typename and decides if it is suitable. In there you can customly decide if type is appropriate.

    DrawContextMenu(property, List<IsTypeSuitable(Typename)>)
     
  34. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    If however you want to restrict types or interfaces of field itself than I can make such feature.

    Let's say you have have Animal class.
    And child classes like Cats(Lion,Tiger), Dogs(wolf, wolf2), Birds(bird1, bird2).
    And you want include only cats and dogs.

    You need to Have a field that is type Animal with serialize reference attribute and additional attribute that restricts child types and possibly interfaces.

    I can make this attribute if it is needed.

    It will look like this:
    SerializeReferenceUIRestriction( IncludeTypes[], ExcludeTypes[])
     
    Ardenian likes this.
  35. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    This goes in the direction that I tried to describe, yes. Maybe helper classes could be an idea. Unity used them somewhere in GUI code, either GUIContent or GUILayoutOption. It would basically look like
    [SerializeReferenceUIRestriction(params TypeRestriction restrictions)]
    , with such derived classes of
    TypeRestriction
    being
    TypeImplementsRestriction
    and
    TypeExtendsRestriction
    . When populating the UI, you would give the list with types into these helper classes, they would filter the list and return the filtered list with all valid types, gradually reducing the total elements in the list.

    An example using your Animals one could be
    [SerializeReferenceUIRestriction(
    new TypeImplementsRestriction(typeof(IFlyingAnimal)),
    new TypeExtendsRestriction(typeof(Bird))]
    .

    Here, only classes deriving from
    Bird
    are shown in the dropdown and only if they implement
    IFlyingAnimal
    . A
    Chicken
    would not be included, for instance, although it is a bird, because it cannot fly as other birds, not implementing the interface.
     
    Last edited: Feb 27, 2020
  36. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    New major update has been done.

    Including big refactoring and clean up and ability to add combinable type restriction attributes.
    Restriction by substring also was added as a foundation for the future searchable menu. (Search UI is just not done yet)

    You can write Restriction Attributes like this. All combinations are supported. You also can add custom restrictions.
    Code (CSharp):
    1.     [Header("Interface with restriction")]
    2.     [SerializeReference]
    3.     [SerializeReferenceButton]
    4.     [SerializeReferenceUIRestrictionIncludeTypes(typeof(AnimalBase))]
    5.     [SerializeReferenceUIRestrictionExcludeTypes(typeof(RedCat), typeof(GoldenCat))]
    6.     [SerializeReferenceUIRestrictionIncludeInterfaces(typeof(ICat), typeof(IDog), typeof(IFish))]  
    7.     [SerializeReferenceUIRestrictionExcludeInterfaces(typeof(IDog))]  
    8.     public IAnimal AnimalWithRestriction = default;
     
    Ardenian, DoctorShinobi and LeonhardP like this.
  37. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    GreatlySimplified type restriction code.
    There is now 2 built in atributes for restriction that can take both type or interface type as a parameter.

    Code (CSharp):
    1.     [Header("Field with multiple restrictions(Include, Exclude - Type, Interface")]
    2.     [SerializeReference]
    3.     [SerializeReferenceButton]
    4.     [SerializeReferenceUIRestrictionIncludeTypes(typeof(MammalBase), typeof(IFish))]
    5.     [SerializeReferenceUIRestrictionExcludeTypes(typeof(RedCat), typeof(IDog), typeof(BlackFish))]
    6.     public IAnimal AnimalWithRestriction = default;    
     
    Ardenian likes this.
  38. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Thank you very much for adding the attributes, very handy!
     
    TextusGames likes this.
  39. quabug

    quabug

    Joined:
    Jul 18, 2015
    Posts:
    66
    @TextusGames
    Thanks for your package, which make `[SerializeReference]` much easier to use in inspector.
    Do you mind if I redistribute this package as part of my open source project? Which license should I follow?
     
    TextusGames likes this.
  40. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Yes, you can use this package in your open source project, as well as anyone else. I do not know that type of license it should be though. Good luck with your projects :)
    Actually the hope is that something like this will be available to every unity user by default.
     
    PassivePicasso and DoctorShinobi like this.
  41. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @TextusGames Does the attribute work with nested classes? I have the following (simplified) code and it doesn't show me anything when using
    [SerializeReferenceButton]
    .

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4. [Serializable]
    5. public abstract class CharacterPropertyReference
    6. {
    7.     public abstract float Value(CharacterData data);
    8. }
    9.  
    10. [CreateAssetMenu()]
    11. public class CharacterData : ScriptableObject
    12. {
    13.     [SerializeField]
    14.     private float life;
    15.  
    16.     [Serializable]
    17.     public class LifePropertyReference : CharacterPropertyReference
    18.     {
    19.         public override float Value(CharacterData data) => data.life;
    20.     }
    21. }
    Code (CSharp):
    1. public class Character : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private CharacterData data;
    5.  
    6.     public CharacterData Data { get => data; }
    7. }
    Code (CSharp):
    1. [ExecuteInEditMode]
    2. public class CharacterVitals : MonoBehaviour
    3. {
    4.     [SerializeField]
    5.     private Character character;
    6.  
    7.     [SerializeReferenceButton]
    8.     private CharacterPropertyReference maximumLife;
    9.  
    10.     private void OnEnable()
    11.     {
    12.         if(maximumLife != null)
    13.         {
    14.             Debug.Log(maximumLife.Value(character.Data));
    15.         } else
    16.         {
    17.             Debug.Log("isNull");
    18.         }
    19.     }
    20. }
    I would expect that the component
    CharacterVitals
    gives me a button to choose a type that derives from CharacterPropertyReference, however, it does not, nor does it show an error message. It just does nothing and the field
    maximumLife
    is null.

    Using
    [SerializeField]
    ,
    using static CharacterData;
    and assigning a new instance of the nested class type
    LifePropertyReference
    does work.
     
    Last edited: Mar 4, 2020
  42. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
    Just to report back to you, the team found this to be an interesting suggestion but couldn't commit to anything further than that at this point due to conflicting priorities and since this would be a bigger undertaking involving multiple teams.

    Discussions are ongoing.
     
    NotaNaN, phobos2077, ihgyug and 4 others like this.
  43. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429

    You forgot to add main attribute [SerializeRefernce]
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [ExecuteInEditMode]
    4. public class CharacterVitals : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     private Character character;
    8.  
    9.     [SerializeReference]
    10.     [SerializeReferenceButton]
    11.     private CharacterPropertyReference maximumLife;
    12.     private void OnEnable()
    13.     {
    14.         if(maximumLife != null)
    15.         {
    16.             Debug.Log(maximumLife.Value(character.Data));
    17.         } else
    18.         {
    19.             Debug.Log("isNull");
    20.         }
    21.     }
    22. }
    upload_2020-3-4_18-56-13.png
     
    Ardenian likes this.
  44. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Thanks for letting to know.
    Maybe in future unity team will find resources to make this default feature.

    Meanwhile, I guess I will maintain it by myself.
     
  45. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    My bad, thank you! It is getting late here and it shows.
     
  46. DoctorShinobi

    DoctorShinobi

    Joined:
    Oct 5, 2012
    Posts:
    219
    Hey, it seems like there's a problem when using this package in 2020.1.0b1. The menu does not seem to show up when using interfaces
    Code (CSharp):
    1. [Header("Field with multiple restrictions")]
    2.     [SerializeReference]
    3.     [SerializeReferenceButton]
    4.     [SerializeReferenceUIRestrictionIncludeTypes(typeof(MammalBase), typeof(IFish))]
    5.     [SerializeReferenceUIRestrictionExcludeTypes(typeof(RedCat), typeof(IDog), typeof(BlackFish))]
    6.     public IAnimal AnimalWithRestriction = default;    
    7.  
    8.     [Header("Interface")]  
    9.     [SerializeReference]
    10.     [SerializeReferenceButton]
    11.     public IAnimal AnimalInterface = default;
    12.    
    13.     [Header("Base Class")]
    14.     [SerializeReference]
    15.     [SerializeReferenceButton]
    16.     public AnimalBase AnimalBaseClass = default;
    17.     [Header("List of interfaces")]
    18.     [SerializeReference]
    19.     [SerializeReferenceButton]
    20.     public List<IAnimal> AnimalsWithInterfaces = new List<IAnimal>();
    21.    
    22.     [Header("List of Animals via MMB menu")]
    23.     [SerializeReference]
    24.     [SerializeReferenceMenu]
    25.     public List<AnimalBase> AnimalsWithInterfacesMenu = new List<AnimalBase>();
    Out of these 5 variables only AnimalBaseClass and AnimalsWithInterfacesMenu will show up.
     
  47. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Hey. Everything is working fine. I just tested this in 2020.1.0b1. Button for interfaces are shown and working. MMB attribute is also working.|

    Try to create empty project in 2020.1.0b1 and improt package and test.
     
  48. The-Author

    The-Author

    Joined:
    Jun 29, 2018
    Posts:
    3
    I happened to be working on my own version of this "assign reference from inspector button" thing before I found this thread. I also ran into the problem with being unable to undo assigning new types (the editor would throw errors and often outright crash without
    ApplyModifiedPropertiesWIthoutUndo()
    ). Eventually I discovered that with this single line of code before assigning a new managedReferenceValue:
    Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Assign New Type");
    , undo seems to work perfectly. I tried it with this package and it seems to work too:

    Code (CSharp):
    1.  
    2.             void AssignNewInstanceOfType(object typeAsObject)
    3.             {
    4.                 Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Change Type");
    5.                 var type = (Type)typeAsObject;
    6.                 var instance = Activator.CreateInstance(type);
    7.                 property.serializedObject.Update();
    8.                 property.managedReferenceValue = instance;
    9.                 property.serializedObject.ApplyModifiedPropertiesWithoutUndo(); // undo is bugged
    10.             }
    11.  
    Of course, I'm just one person. There may be bugs I haven't encountered.
     
  49. DoctorShinobi

    DoctorShinobi

    Joined:
    Oct 5, 2012
    Posts:
    219
    Weird. I'll try to isolate the problem and will update if I find out how to reproduce this.
     
  50. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Undo was and maybe still is buggy with serializedReference on Unity side. Once it is fixed by unity applyModifiedPropertiesWithUndo can be used.