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. The-Author

    The-Author

    Joined:
    Jun 29, 2018
    Posts:
    3
    Right, I just wanted to share a workaround I found that I’m using until unity does fix it.
     
    TextusGames likes this.
  2. marsheleene

    marsheleene

    Joined:
    Jan 11, 2019
    Posts:
    1
    Hi!

    I am working on a similar system and I have a problem with custom drawers for the sub-classes. I tested on your package and I also found the problem.

    Let's say I want to draw each sub-class differently. I would write a RedCatDrawer, a GoldenApeDrawer, and so on (I could also use the class hierarchy in order to write a CatDrawer, an ApeDrawer, etc..).

    Let's have this drawer (very simple, this is for the sake of the example):

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(RedCat))]
    5. public class RedCatDrawer : PropertyDrawer
    6. {
    7.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    8.     {
    9.         EditorGUI.LabelField(position, label, new GUIContent("This is a red cat!"));
    10.     }
    11. }
    and this test class:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Test2 : MonoBehaviour
    4. {
    5.     public RedCat redCat = new RedCat();
    6.  
    7.     [SerializeReference]
    8.     public IAnimal anotherRedCat = new RedCat();
    9.  
    10.     [SerializeReference]
    11.     [SerializeReferenceButton]
    12.     public IAnimal animal = default;
    13. }
    If I select RedCat in the dropdown menu, I have this result:
    Capture.PNG
    The default drawer is used instead of my custom one.

    I have a similar issue in my system where the dynamic property which is instanciated by a dropdown menu is not using its custom drawer (it works with default drawers).

    Am I missing something?
     
  3. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Then you draw same field from property attribute custom property has no effect. I someone knows how to force to draw with custom property it will be helpful.
     
  4. pyjamads

    pyjamads

    Joined:
    Jun 12, 2013
    Posts:
    8
    Thanks for making this awesome util to TextusGames!

    Made a small modification to the SerializeReferenceTypeNameUtility:

    Code (CSharp):
    1.  
    2. public static string GetShortNameFromClassName(string className)
    3. {
    4.     var typeDomainSplit = className.Split('.');
    5.    
    6.     return typeDomainSplit[typeDomainSplit.Length - 1];
    7. }
    8.  
    To make the SerializeReferenceInspectorButton show the className without the namespace:

    Code (CSharp):
    1.  
    2. var shortName = SerializeReferenceTypeNameUtility.GetShortNameFromClassName(className);
    3. if (GUI.Button(buttonPosition, new GUIContent(shortName, className + "  ( "+ assemblyName +" )" )))
    4.             property.ShowContextMenuForManagedReference(filters);
    5.  
    And did give the same treatment to the ContextMenu.

    Code (CSharp):
    1.  
    2. void AddContextMenu(Type type, GenericMenu genericMenuContext)
    3.             {
    4.                 var assemblyName =  type.Assembly.ToString().Split('(', ',')[0];
    5.                 var entryName = type + "  ( " + assemblyName + " )";
    6.                 var shortName = SerializeReferenceTypeNameUtility.GetShortNameFromClassName(type.ToString());
    7.                 var item = new GUIContent(shortName);
    8.                 item.tooltip = entryName;
    9.                 genericMenuContext.AddItem(item, false, AssignNewInstanceOfType, type);
    10.             }
     
    Last edited: Apr 19, 2020
    TextusGames likes this.
  5. pyjamads

    pyjamads

    Joined:
    Jun 12, 2013
    Posts:
    8
    However I'm struggling with crashes, when the instances are assigned to the managedReferenceValue, when the SerializeReferenceButton tag is on lists... and it's super slow to add the instance when it actually works, like 3-5 secs.

    seems to be a SerializeReference issue, when you're replacing elements in a List, it seems like it's trying to access the serialized object with an id before it's been properly created.

    I've tried on 2019.3.0f6 and 2019.3.10f1, same issue seems to happen...

    Might be this bug: https://issuetracker.unity3d.com/is...s-performed-after-parent-classs-serialization
    Switching to 2020.2 didn't work, but I've tried a different list of objects, and they don't seem to have this problem.

    I think my issue is related to having a List that's also serializeReference, on each item in the serializeReference list...
     
    Last edited: Apr 20, 2020
  6. jasonatkaruna

    jasonatkaruna

    Joined:
    Feb 26, 2019
    Posts:
    64
    Hey there, I'm looking through Unity's Csharp reference, and it looks like you can call
    EditorGUI.PropertyField
    and it will return the field that the inspector thinks it should draw for that property. Here at line 6483:

    Code (CSharp):
    1. internal static bool PropertyFieldInternal(Rect position, SerializedProperty property, GUIContent label, bool includeChildren)
    2. {
    3.     return ScriptAttributeUtility.GetHandler(property).OnGUI(position, property, label, includeChildren);
    4. }
    That
    ScriptAttributeUtility.GetHandler
    method returns the
    PropertyDrawer
    that unity expects for the inspector. That is, if there's a CustomPropertyAttribute, it will use that, if not, it will use the default drawer.

    If you want to take a look yourself:
    line 6483: https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/EditorGUI.cs
    line 427: https://github.com/Unity-Technologi.../ScriptAttributeGUI/ScriptAttributeUtility.cs

    The files are enormous, but good thing there's ctrl+f lol.
     
  7. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    I am already using this. But I am using it from property Attribute Gui. And apparently it does not respect custom property drawer if you want to draw the same property. Instead it just draws default gui.
     
  8. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    I would just like to say, TextusGames, you are my hero.

    I spent a few days trying to find a way to properly support polymorphism in the editor. It blew my mind that Unity didn't support this by default.

    Finally I found your package, and it has solved all my problems. You're the best.
     
    phobos2077 and TextusGames like this.
  9. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    There seems to be an issue if the base class is abstract. I realize you added code to make it so it doesn't show abstract classes, which of course makes sense but It'd be cool if the base class is allowed to be abstract, but won't show up in the menu :)) Also of course, the default value would be null. This is all assuming this is even possible, since maybe it's a unity thing.

    Question - do you plan on putting this on github or something, to make it easier for fixes to be made / people to fork? If not, do you mind if I make a fork of it on github?
     
    Last edited: May 16, 2020
  10. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    I tested and found no issue with abstract classes. The base abstract class is not shown in menu becouse it is abstract and it is not allowed to be instantiated. The childs of this base abstract class are shown and can be instantiated.
    They can be accessed either by interface field or buy field declared as abstract class or any other parent class.

    You can write :

    Code (CSharp):
    1. [Serializable]
    2. public abstract class AbstractAnimal : IAnimal
    3. {
    4.     [SerializeField] protected float age;
    5.     public GameObject food;
    6.     public virtual void Feed()
    7.     {
    8.         Debug.Log("Thanks");
    9.     }
    10. }
    11.  
    12. [Serializable]
    13. public class AbstractAnimalChild : AbstractAnimal {}

    and access it in other class (monobehaviour/ scriptable object)
    Code (CSharp):
    1.     [Header("Interface")]  
    2.     [SerializeReference]
    3.     [SerializeReferenceButton]
    4.     public AbstractAnimal AnimalAbstract = default;  
    upload_2020-5-16_10-7-9.png

    As for github, I think I can create github branch. I just have not ever done this. I will research how to do that and If it is not dificult I will put the link here. But for the average end-user I think it is easier to download just package directly from here and import it in project.
     
    phobos2077 and joshcamas like this.
  11. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Also renaming bug is not still fixed by unity in serialized refference itself.
    Renaming used class produce error and all variables appears to be resetted to null. But if you enter play mode and exit data will be shown as it was before.

    upload_2020-5-16_10-26-10.png

    If such issues still exists it is demotivaing and risky to work with SerializedReference.
     
    phobos2077 and ihgyug like this.
  12. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
  13. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Since it is my first experience with github, can you try to use this repository and confirm that everything is normal?
     
  14. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    Looks good! Thank you!!
     
    TextusGames likes this.
  15. Pritchard

    Pritchard

    Joined:
    Aug 30, 2013
    Posts:
    5
    Hi, first of all - THANK YOU! What an amazing addon.

    I seem to have a strange bug though - demonstrated in this video:

    I'm not sure what is going on - is this related in any way to the issues discussed by Ardenian?
    Unfortunately, right now this is a showstopper for me - but hopefully it can be resolved!

    EDIT: I read up a bit more on related issues and it seems to have something to do with Prefab Instances. I unpacked my prefabs and the issue went away - so I suppose unless Unity themselves fix the issue, that'll be the path forward.
     
    Last edited: May 26, 2020
  16. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Yes. SerializedReference is not working in prefab's instances as expected. It is a unity's thing.
    You however can use it in scriptable objects and in not prefab instances.
    However there is at least one significant bug with remaming used type. Unity will through Uncknown managed type exception and all fields will be show null until you press play.
    And this is a show stoper. I would not recommend to use SerializeReference until they fix renaming issue. Or until they actually say that it is ready for production.
    As for me I may risk to try to use it in my next game and test it.
     
    Last edited: May 26, 2020
  17. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Using this great attribute, something that I keep stumbling on is that your code takes the class name literally. It would be a great enhancement if one could change the display name of a class for a much cleaner look. If I have an abstract base class
    CharacterProperty
    , for instance, and then children from it called
    EnduranceProperty
    , then in the dropdown, it will literally be called
    EnduranceProperty
    , although it would be nice if it was just called
    Endurance
    .

    I am not an expert in C# naming conventions, so I don't know if it would be appropriate to just name it "Endurance" instead, nonetheless it would be great if one had some control over how the class name is displayed in the dropdown of the button. Maybe an attribute
    [SerializeReferenceName]
    for classes that if present, changes how the class is displayed in the dropdown?
     
  18. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Than you add monobehaviour to gameobject it is also displayed as it's class name. So I guess it is normal for unity.

    But I am also displaying additional information like assembly and namespace(it was made in order to differintiate classes which has exact same name but live in different namespace and assembly). I think you are strugling with this. I can add an option to show just class name and make it default one.
    So previously displayed entry -
    "TextusGamesNamespace.EnduranceProperty (ExampleAssembly)"
    Will be displayed as
    "EnduranceProperty"
     
    Ardenian likes this.
  19. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Oh, it is handy, yes, especially if you have multiple classes from different assemblies or namespaces, so I see why it is useful. I admit that it is a little bit of nitpicking from me here. I just happen to encounter a couple of cases that would profit if you could customize how the classes are displayed in the dropdown.

    One of these cases is selecting an object that implements some logic, for instance how to generate a procedural mesh. Different classes from the shared abstract base class all have their own implementation on how to approach this. Now it would be neat if I could choose a "method" from this dropdown, like "Cellular Automata" or "Binary Seach Partition", but instead I get the literal classes names, like "CellularAutomataGenerator", so it would be a little enhancement if one could change this, allowing to choose a method insteaf of a generator, from a user's point of view. This is what I tried to explain with "choosing an enumeration value" in my previous point.

    If you have some spare time, I wouldn't mind a setting to hide the assembly, but I think the benefit is too low to warrant your time. If I have some time on the weekend, I will try to extract the naming convention from your code and allow full customization via attributes, which fits my need.
     
  20. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    Is there a way to use SerializeReference to assign a Serializable in an SO to a field in another Serializable within the same SO?

    For example, let's say I have:


    Code (CSharp):
    1.     public class AbilityInfo : ScriptableObject
    2.     {
    3.         [SerializeReference]
    4. #if UNITY_EDITOR
    5.         [SerializeReferenceButton]
    6. #endif
    7.         public List<AbilityInputInfo> inputInfos;
    8.  
    9.         [SerializeReference]
    10. #if UNITY_EDITOR
    11.         [SerializeReferenceButton]
    12. #endif
    13.         public List<EffectInfo> abilityEffects;
    14.     }
    Code (CSharp):
    1. [System.Serializable]
    2.     public class GroundSelectInputInfo : AbilityInputInfo
    3.     {
    4.             //some contents
    5.     }
    Code (CSharp):
    1.     public class MoveEffectInfo : EffectInfo
    2.     {
    3.         [SerializeReference]
    4.         public GroundSelectInputInfo input;
    5.    }
    With this setup, I can add a GroundSelectInputInfo to inputInfos in an AbilityInfo ScriptableObject. I can then add a MoveEffectInfo to the abilityEffects of the same ScriptableObject. But is there a way to then link the GroundSelectInputInfo object to the MoveEffectInfo object by assigning it to the input field ?
     
  21. jasonatkaruna

    jasonatkaruna

    Joined:
    Feb 26, 2019
    Posts:
    64
    Since they are the same Serialized Asset, I believe you can relate the two object ids via code. I haven't tried it though.

    [SerializeReference] works by setting a reference ID in the yaml file, then describing those ids at the end of the file. The IDs are local to that asset file.

    If you wanted to relate them in the inspector, how would you like to do it? Drag-n-drop like the
    UnityObject
    references?
     
  22. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Currently, unity does not have inspector options to copy by value or copy by reference (that is that you need I guess). Moreover, they do not provide low-level API to get value of SerializedProperty.managedReference. So I see no way to copy paste a SerializeReference field in inspector time. If they provide such options I can add it as menu items. But It is likely that they will be available in the right-click menu by default (by unity) (at least I hope so).

    I, however, think that you can copy-paste by reference in OnValidate() void of Scriptable object or monobehaviour.
    This is of course not a generic solution.
     
  23. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    Thanks again for this wonderful asset!

    I have a bug I've been running into. For some of my ScriptableObjects that have nested SerializeReference inside of them, those SerializeReference fields are constantly being reset to null. I think it might be related to having once renamed some of my classes, but even after restarting Unity it doesn't fix the issue. In fact every time I restart Unity, the fields are reset to null.

    Is there some way around this or some way to fix this?

    edit: Actually I just updated to 2020.1.1f1, and it fixed the bug. Hallelujah!!!!
     
    Last edited: Aug 9, 2020
  24. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    I found a bug while cloning the implementation of the
    SerializeReferenceButtonAttribute
    for exercise purposes. If the type has no parameterless constructor, an exception is thrown:
    The exception can be prevented by wrapping an IF-Statement before creating an instance of the newly selected type, thus preventing confusion for users and clearly telling them what is going on:
    Code (CSharp):
    1.         if (type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null)
    2.         {
    3.             var serializedProperty = property;
    4.             var instance = Activator.CreateInstance(type);
    5.  
    6.             serializedProperty.serializedObject.Update();
    7.             serializedProperty.managedReferenceValue = instance;
    8.             serializedProperty.serializedObject.ApplyModifiedProperties();
    9.         } else
    10.         {
    11.             Debug.LogWarning($"Failed to create an instance of type \'{type}\' because the type does not provide a parameterless constructor.");
    12.         }
    The condition itself is taken from How do I check if a type provides a parameterless constructor?

    If someone is interested in a light version of the
    SerializeReferenceButtonAttribute
    , this is a version that I wrote for exercise purposes based on the code by @TextusGames:
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. using Object = UnityEngine.Object;
    7.  
    8. [CustomPropertyDrawer(typeof(SerializeReferenceButtonAttribute))]
    9. public class SerializeReferenceButtonAttributeDrawer : PropertyDrawer
    10. {
    11.     private static readonly Color backgroundColor = new Color(0.75f, 0.75f, 0.75f, 1);
    12.  
    13.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    14.     {
    15.         return EditorGUI.GetPropertyHeight(property, true);
    16.     }
    17.  
    18.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    19.     {
    20.         EditorGUI.BeginProperty(position, label, property);
    21.  
    22.         var labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
    23.         EditorGUI.LabelField(labelPosition, label);
    24.  
    25.         DrawSelectionButton(property, position);
    26.  
    27.         EditorGUI.PropertyField(position, property, GUIContent.none, true);
    28.  
    29.         EditorGUI.EndProperty();
    30.     }
    31.  
    32.     private void DrawSelectionButton(SerializedProperty property, Rect position)
    33.     {
    34.         var buttonPosition = position;
    35.         buttonPosition.x += EditorGUIUtility.labelWidth + 1 * EditorGUIUtility.standardVerticalSpacing;
    36.         buttonPosition.width = position.width - EditorGUIUtility.labelWidth - 1 * EditorGUIUtility.standardVerticalSpacing;
    37.         buttonPosition.height = EditorGUIUtility.singleLineHeight;
    38.  
    39.         var previouIndentLevel = EditorGUI.indentLevel;
    40.         EditorGUI.indentLevel = 0;
    41.         var previousBackgroundColor = GUI.backgroundColor;
    42.         GUI.backgroundColor = backgroundColor;
    43.  
    44.         string fullTypeName = property.managedReferenceFullTypename;
    45.         string text = string.IsNullOrWhiteSpace(fullTypeName) ? "\'null\'" : fullTypeName;
    46.  
    47.         if (GUI.Button(buttonPosition, new GUIContent(text)))
    48.         {
    49.             ShowContextMenu(property);
    50.         }
    51.  
    52.         GUI.backgroundColor = previousBackgroundColor;
    53.         EditorGUI.indentLevel = previouIndentLevel;
    54.     }
    55.  
    56.     private void ShowContextMenu(SerializedProperty property)
    57.     {
    58.         var contextMenu = new GenericMenu();
    59.         FillContextMenu(contextMenu, property);
    60.         contextMenu.ShowAsContext();
    61.     }
    62.  
    63.     private void FillContextMenu(GenericMenu contextMenu, SerializedProperty property)
    64.     {
    65.         contextMenu.AddItem(new GUIContent("\'null\'"), false, SetManagedReferenceToNull, property);
    66.         var types = GetDerivedTypes(property);
    67.         foreach (Type type in types)
    68.         {
    69.             contextMenu.AddItem(new GUIContent(type.Name), false, SetManagedReferenceToNewInstance, (type, property));
    70.         }
    71.     }
    72.  
    73.     private void SetManagedReferenceToNull(object userData)
    74.     {
    75.         var serializedProperty = (SerializedProperty) userData;
    76.  
    77.         serializedProperty.serializedObject.Update();
    78.         serializedProperty.managedReferenceValue = null;
    79.         serializedProperty.serializedObject.ApplyModifiedProperties();
    80.     }
    81.  
    82.     private void SetManagedReferenceToNewInstance(object userData)
    83.     {
    84.         (Type type, SerializedProperty property) = ((Type type, SerializedProperty property)) userData;
    85.  
    86.         if (type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null)
    87.         {
    88.             var serializedProperty = property;
    89.             var instance = Activator.CreateInstance(type);
    90.  
    91.             serializedProperty.serializedObject.Update();
    92.             serializedProperty.managedReferenceValue = instance;
    93.             serializedProperty.serializedObject.ApplyModifiedProperties();
    94.         } else
    95.         {
    96.             Debug.LogWarning($"Failed to create an instance of type \'{type}\' because the type does not provide a parameterless constructor.");
    97.         }
    98.     }
    99.  
    100.     private IEnumerable<Type> GetDerivedTypes(SerializedProperty property)
    101.     {
    102.         string typeName = property.managedReferenceFieldTypename;
    103.  
    104.         var typeSplitString = typeName.Split(char.Parse(" "));
    105.         var typeClassName = typeSplitString[1];
    106.         var typeAssemblyName = typeSplitString[0];
    107.         Type type = Type.GetType($"{typeClassName}, {typeAssemblyName}");
    108.         //Debug.Log($"type={type}, assembly={type.Assembly}");
    109.  
    110.         if (type != null)
    111.         {
    112.             var derivedTypes = TypeCache.GetTypesDerivedFrom(type);
    113.             foreach (Type derivedType in derivedTypes)
    114.             {
    115.                 //Debug.Log($"type={derivedType.Name}, subclass={derivedType.IsSubclassOf(typeof(Object))}, abstract={derivedType.IsAbstract}");
    116.                 if (!derivedType.IsSubclassOf(typeof(Object)) && !derivedType.IsAbstract)
    117.                 {
    118.                     yield return derivedType;
    119.                 }
    120.             }
    121.         }
    122.     }
    123. }
    Comparing the two versions:
    • The original version uses extension methods on properties and static classes that are called from the property drawer, while I put everything in the property drawer itself and thus everything in one file (except the attribute, of course) without extension methods or static classes.
    • My version has a lot less error checking, or better, barely any at all, so if something goes wrong, it does so big time.
    • Type name and its assembly name are nicely formatted in the original version, I put them in as they are without any formatting.
    • A struct is introduced and used in the original version to bundle a type and a serialized property when creating an instance of a selected type, whereas I use a named tuple, which is most likely slower.
     
    TextusGames likes this.
  25. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Thanks.
    I would rather filter out types that does not have empty constructor and do not show them in menu.
    If there is no generic way to create types without empty constructor.
     
    Last edited: Aug 13, 2020
    Ardenian likes this.
  26. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Fixed. But structs can still be created (strangely) so I decided to give them a chance :).
     
    Ardenian likes this.
  27. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    This is actual a lot better, preventing the issue before it becomes a problem!

    In my solution, instead of filtering the type out, I added a disabled item to the generic menu:
    Code (CSharp):
    1.         var types = GetDerivedTypes(property);
    2.         foreach (Type type in types)
    3.         {
    4.             var content = new GUIContent(type.Name);
    5.             if (type.IsValueType || type.GetConstructor(Type.EmptyTypes) != null)
    6.             {
    7.                 contextMenu.AddItem(content, false, SetManagedReferenceToNewInstance, (type, property));
    8.             } else
    9.             {
    10.                 contextMenu.AddDisabledItem(content);
    11.             }
    12.         }
    However, I dislike that in neither of our solutions, the user gets to know what is going on. Whether the type doesn't appear in the menu at all or whether it shows it disabled, the user has no way to receive feedback why they cannot select that type. I don't think there is any easy way to do this in Unity, though, other than just throwing in a console message which isn't a good style in my modest opinion. Would prefer an alert box or something, or a tooltip for the disabled item.

    EDIT: Okay, there is actually an overload for GUIContent that takes both text and tooltip, but why doesn't it show the tooltip in the generic menu? Is that intended behaviour or a bug?

    Code (CSharp):
    1. var content = new GUIContent(type.Name, "This is a tooltip!");
     
    Last edited: Aug 13, 2020
  28. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    The solution to communicate to user that types he can use is to write a known limitations in readme.)
     
  29. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Sounds reasonable!

    As a little QoL, I moved the button for selecting the type into the foldout of its property. Right now, the button is there even if you have not expanded the serialized property, so I don't draw the button if the serialized property does have an instance assigned (is null), but is not expanded, but if the value of the serialized property is null, I do draw the button so that you can assign an instance.

    Code (CSharp):
    1.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    2.     {
    3.         if (string.IsNullOrEmpty(property.managedReferenceFullTypename) || !property.isExpanded)
    4.         {
    5.             return EditorGUI.GetPropertyHeight(property, true);
    6.         } else
    7.         {
    8.             return EditorGUI.GetPropertyHeight(property, true) + EditorGUIUtility.singleLineHeight;
    9.         }
    10.     }
    Code (CSharp):
    1.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    2.     {
    3.         EditorGUI.BeginProperty(position, label, property);
    4.  
    5.         var labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
    6.         EditorGUI.LabelField(labelPosition, label);
    7.  
    8.         EditorGUI.PropertyField(position, property, GUIContent.none, true);
    9.  
    10.         if (string.IsNullOrEmpty(property.managedReferenceFullTypename))
    11.         {
    12.             DrawSelectionButton(property, position);
    13.         }
    14.         else if(property.isExpanded)
    15.         {
    16.             DrawSelectionButton(property, new Rect(position.x, position.y + EditorGUI.GetPropertyHeight(property, true), position.width, position.height));
    17.         }
    18.         EditorGUI.EndProperty();
    19.     }
     
  30. jasonboukheir

    jasonboukheir

    Joined:
    May 3, 2017
    Posts:
    84
    I was able to achieve this in my custom inspector by using reflection and an extension method, SerializedProperty.GetValue. You can find the code here: https://github.com/CareBoo/Serially...tor/Extensions/SerializedProperty/GetValue.cs

    the jist is that you traverse the serialized object's target from the given property path.
     
    TextusGames likes this.
  31. HassanKhallouf

    HassanKhallouf

    Joined:
    Mar 5, 2015
    Posts:
    52
    Thanks, this is actually great!
    Is there a reason Unity didn't make a getter for the value? is this something to be implemented later or there is a limitation to the current implementation ?
     
  32. Limnage

    Limnage

    Joined:
    Sep 12, 2013
    Posts:
    41
    Do custom drawers not work with SerializeReference objects? I was trying create an enum flag field with the [EnumMask] custom drawer (https://forum.unity.com/threads/property-drawer-for-enum-flags-masks-download.517750/) inside one of my Serializable classes that I create with the SerializeReferenceButton. However, this causes :

    ArgumentException: Field flags defined on type B is not a field on the target object which is of type A.
    where I have Class A, which is a ScriptableObject, which has a SerializeReference field of type B, which has a flag enum field that I annotated with this property.

    I can't tell if it's an issue with EnumMask or SerializeReference.
     
  33. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    Edit1: [One workaround is that you must make an editor script yourself for the Scriptable object.]

    Sadly this is not usable in the scriptable objects due to this bug:


    Basically in the case of the scriptable object, if you unfold the polymorphic type's instance in the editor to set some values on the fields, it's fine. But if you keep it unfolded and enter play mode, the entire inspector editor for the scriptable object becomes invisible as demonstrated in the video above. This bug is only for the scriptable objects, everything works fine in normal mono-behaviors.

    Code used in the sample:
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. namespace Animal
    5. {
    6.     [CreateAssetMenu(fileName = "New Data", menuName = "Create data", order = 1)]
    7.     public sealed class SampleData : ScriptableObject
    8.     {
    9.         [Header("Some animals. You will be able to assign in editor " +
    10.             "but they will be gone upon play mode")]
    11.         [SerializeReference]
    12.         [SerializeReferenceButton]
    13.         List<IMyAnimals> animals;
    14.     }
    15.  
    16.     public interface IMyAnimals
    17.     {
    18.         public void Bark();
    19.     }
    20.  
    21.     public class MyDog : IMyAnimals
    22.     {
    23.         [SerializeField] string dogName;
    24.         void IMyAnimals.Bark()
    25.         {
    26.  
    27.         }
    28.     }
    29.  
    30.     public class MyCat : IMyAnimals
    31.     {
    32.         [SerializeField] string catName;
    33.         void IMyAnimals.Bark()
    34.         {
    35.  
    36.         }
    37.     }
    38. }
     
    Last edited: Apr 15, 2021
  34. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    Which uni
    Which unity version do you use?
    Does this happens if you manually create list of IMyAnimals without SerializReference button?
    [SerializeReference] List<IMyAnimals> unityDrawnAndimals = new List<IMyAnimals> {new MyCat(), new MyDog()};
     
  35. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    Unity version: 2020.3.3f1 LTS
    I used the SerializeReference button(that the plugin provides) to create instances.
     
  36. Maisey

    Maisey

    Joined:
    Feb 17, 2014
    Posts:
    302
    I hope Unity takes this feature seriously (shameful poke at @LeonhardP )
    This has been my one and only big feature request for years (literally years). Once SerializedReference came I got happy and then sad because we still needed to make custom inspectors to actually use this in a meaningful way for anyone that's not a programmer.
    FullInspector & Odin has had this kind of support for ages but I wish to not rely on 3rd part tech for such a "simple" feature.
     
    Last edited: Apr 30, 2021
  37. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    One thing to note for everybody using this:
    This does not support generic interfaces.
    What does this imply?
    For a generic interface like this:
    Code (CSharp):
    1. public interface IWork<T>
    2.     {
    3.         public void Execute(T t);
    4.         internal void PrepareDepInternal();
    5.     }
    **Edit I am using C# 8.0, thus access modifier in interface works(without protected, private etc due to 'default method implementation' being unsupported by unity).

    Then in your scriptable object(given that you used the fix I described in the above post of mine) or MonoBehaviour, if you use this,

    Code (CSharp):
    1. [SerializeReference]
    2.         [SerializeReferenceButton]
    3.         List<IWork<Actor>> workList;
    Then it will not work!

    While this plugin is really great, you should be careful with generic stuff along with the AOT issues.
     
  38. jasonboukheir

    jasonboukheir

    Joined:
    May 3, 2017
    Posts:
    84

    IIRC Unity doesn't support [SerializeReference] on generics anyway
     
    kaiyum and TextusGames like this.
  39. TextusGames

    TextusGames

    Joined:
    Dec 8, 2016
    Posts:
    429
    This is most likely a regression from Unity side.
    https://github.com/TextusGames/UnitySerializedReferenceUI/issues/7
     
  40. bartofzo

    bartofzo

    Joined:
    Mar 16, 2017
    Posts:
    151
    Using this on a list inside a Scriptable Object with the button attribute immediately hides the inspector. Sometimes it's visible for one frame but then it's gone. Fixed it by making a custom inspector for the scriptable object. I'm on unity 2020.3.11f1 LTS. Great tool otherwise!
     
    kaiyum likes this.
  41. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    I believe that was exactly the issue I have faced. This should be standard unity feature though
     
  42. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    Thank you for that tool!!
    I cannot understand why it's not part of Unity.

    I am having an issue with the lists, maybe someone can help.
    I have integrated the repo in an empty project, and modified 'ApeBase' to add a color:
    Code (CSharp):
    1. public abstract class ApeBase : MammalBase, IApe
    2. {
    3.     public string Tag = default;
    4.  
    5.     public Color TheColor = Color.white;
    6. }
    Then, I have added the 'Test' to the camera and I have created 2 apes.
    I can change the color of the first ape without any problem:
    First ape color.png

    Now to the problem, here is what happen if I click on the color of the second ape:
    Second ape color bug.png
    The whole inspector is 'compressed' and the new color will not be taken into account.

    Note that it also happens with the food (or anything which requires an additional inspector window I suspect).
     
  43. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    Note that if you add a try-catch in 'SerializeReferenceButtonAttributeDrawer.OnGUI()', and you completely ignore the exception, then the coloring will work for the first and second apes, but not the following ones.

    And strangely enough, the food will work for all the apes...

    [edit] Well, in fact the color will not work only if you click on the surround colored circle, if you use the inner cube it will work.
    And once you have used the inner cube for an element, the surrounding circle will work fine for that element.
     
    Last edited: Jun 20, 2021
  44. kaiyum

    kaiyum

    Joined:
    Nov 25, 2012
    Posts:
    686
    Create a custom inspector for your component and draw the fields with it.
    https://docs.unity3d.com/Manual/editor-CustomEditors.html
    Look up above my issue about the inspector.
     
  45. Gladyon

    Gladyon

    Joined:
    Sep 10, 2015
    Posts:
    389
    Well, I don't really want to recreate a custom editor for every Unity types which is using a window.
    The try-catch patch is working nicely.

    What happen under the hood is that Unity is throwing an exception on purpose when a window is opened.
    I don't know exactly why they do that, I haven't followed their code that far.
    Anyway, catching the Unity exception and ignoring it (which is exactly what Unity is doing in their own code) make it works nearly completely, which is enough for me.
     
  46. michaelday008

    michaelday008

    Joined:
    Mar 29, 2019
    Posts:
    135
    What an awesome project/utility!

    I'm going to be using this in the next release of AnyRPG. I will be happy to credit you and link to any web site you want in the in-engine credits. If I don't hear from you, I'll just link to the github repo where you posted this.

    You solved a big problem I was having. The quest system previously had to have a whole bunch of empty serialized properties for objective types that may or may not be used because just serializing the base class wouldn't result in the proper objective tracking logic being run.

    Before and after pictures below. What a difference :)

    Before:
    upload_2021-9-19_23-2-52.png

    After:
    upload_2021-9-19_23-4-2.png
     
    TextusGames and joshcamas like this.
  47. fuser

    fuser

    Joined:
    Nov 23, 2013
    Posts:
    10
    Thanks for the excellent feature @TextusGames. I've been using this for a year or so now and am very happy with it.

    I'd like the drop down list for SerializeReferenceButton to include the target class itself. I've implemented this locally as a change to ScriptAttributeUtility.cs line 46:

    Code (CSharp):
    1.     public static IEnumerable<Type> GetAppropriateTypesForAssigningToManagedReference(Type fieldType, List<Func<Type, bool>> filters = null)
    2.     {
    3.         var appropriateTypes = new List<Type>();
    4.  
    5.         // Get and filter all appropriate types
    6.         var derivedTypes = TypeCache.GetTypesDerivedFrom(fieldType);
    7.         foreach (var type in derivedTypes) // <- old line
    8.         foreach (var type in derivedTypes.Prepend(fieldType)) // <- replacement line
    9.  
    Are there any reasons why I shouldn't do this? It seems to work fine with my 5 minutes of testing, and it looks like it will be filtered the same way as the subclasses.
     
  48. Rayeloy

    Rayeloy

    Joined:
    Feb 12, 2017
    Posts:
    45
    Hello, this feature is awesome but it is not working for me at all. I can't choose any option as I only get Null (Assign). I'm trying to assign a variable in an scriptable object. The class has many children classes, but none appear.
    upload_2022-3-16_22-50-25.png
     
  49. michaelday008

    michaelday008

    Joined:
    Mar 29, 2019
    Posts:
    135
    Are the child classes set to System.Serializable?
     
    kaiyum likes this.
  50. Rayeloy

    Rayeloy

    Joined:
    Feb 12, 2017
    Posts:
    45
    they were, yes