Search Unity

AssetReference drawer in custom editor

Discussion in 'Addressables' started by merpheus, Aug 19, 2019.

  1. merpheus

    merpheus

    Joined:
    Mar 5, 2013
    Posts:
    202
    Hey, I need to use addressables in custom editor windows. When I use ObjectField for it, it doesn't show that propertydrawer.

    How can I use default AssetReference property drawer in my custom editor window w/o using serializedObject ?

    P.S I am using UIElements, not imgui
     
    Last edited: Aug 19, 2019
    jimmack likes this.
  2. LeonardoADS

    LeonardoADS

    Joined:
    Aug 10, 2014
    Posts:
    10
    Did you find a way to do it?
     
  3. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    182
    I also wanted to use EditorGUILayout.ObjectField, but as AssetReference doesn't extend Object, it isn't possible. =(
    The only way I found how to use AssetReferenceDrawer in a custom editor window is using EditorGUILayout.PropertyField.
    Unfortunately it gives side effect of drawing labels for me, so I'd be glad if Unity developers would create something similar to ObjectField functionality for AssetReferences, as I want to draw custom labels too.
    BTW, first I forgot how to access array elements through SerializedObject.FindProperty, so for anyone like me, the syntax is as follows: "arrayField.Array.data[index]" instead of just "arrayField[index]"
     
  4. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    This is broken and unacceptable. All my inspectors are custom, so now I basically can't offer Addressable support because there's no way to display it in a custom inspector. Do we have to wait or????
     
    MrDizzle26 and Real_Siege like this.
  5. TangentInteractive

    TangentInteractive

    Joined:
    Oct 30, 2019
    Posts:
    4
  6. davidla_unity

    davidla_unity

    Unity Technologies

    Joined:
    Nov 17, 2016
    Posts:
    763
    We can look into it. If anyone has filed a case with Unity please post the case number below.
     
  7. melissae

    melissae

    Unity Technologies

    Joined:
    Dec 5, 2018
    Posts:
    9
    Due to other priorities we will not be addressing this issue at this time.
     
  8. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Really, the feature shouldn't have released without a good way to use it in custom inspectors. Anyway...
     
    Real_Siege and SugoiDev like this.
  9. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Bumping this post, how realistic is the chance of seeing this becoming available within the year? Asking so I know whether or not to completely rebuild part of my inspectors.
    I'm seconding Jerotas here though, not providing support for Custom Inspectors, a feature Unity is touting as a core strength everyone should make use of, is pretty unacceptable for a verified package.
    Please do better, Unity.
     
    MrDizzle26 and davidrochin like this.
  10. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Well we found a workaround, which is using the serializedObject in the custom inspector. I can post some code if you like. At this point I don't care if it's fixed as long as I could make it work - and it does.
     
  11. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    That would be much appreciated! The less I have to rework because of this the better.
     
  12. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Ok, so if you have an AssetReference field called audioClipAddressible and you're doing an Inspector for SoundGroupVariation, then use this code in your custom Inspector. I like to use the nameof operator here so it won't compile from a typo mistake.
    Code (csharp):
    1.  
    2. serializedObject.Update();
    3. EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(SoundGroupVariation.audioClipAddressable)), true);
    4. serializedObject.ApplyModifiedProperties();
    5.  
    The serializedObject thing is I guess built into custom Inspectors. Never used it before now.
     
  13. BarbaraTheGoodEvil

    BarbaraTheGoodEvil

    Joined:
    Mar 2, 2018
    Posts:
    4
    Are there plans to address this at all? I've just started integrating Addressables into some of our custom editors (of which we have quite a few) and it's unfortunate that Addressables and custom editors don't seem to be working well together at all at the moment.
    While I was able to use the PropertyField solution as a workaround that is not really the way I wanted to do it, plus there is the problem that I can't get rid of the labels because of the problem mentioned here.
     
  14. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Yes, unfortunately that simply won't do for me since the asset reference objects are actually not being serialized.
    My use case is this: I'm having classes with a set of conditions that need to be designed in the editor.
    To properly make this work I have a set of Condition classes, each with their own fields and logic, and then try to serialize these within a collection so you can easily add and remove Conditions wherever they're needed.
    Since Unity does not support polymorphic serialization you can't just do that however, everything will get serialized to the base class which is useless. To combat this I built up a system where I actually serialize each Condition as a string denoting its type and a Json with its data.
    This is working pretty great so far, but AssetReferences are a bloody pain having no non-serialized object drawers.
    I'm currently trying to hack something together using the AssetReferenceDrawer class found within the package, but it's not very well-commented and makes use of some internal classes that seem crucial, putting me basically at a point it's impossible to create anything remotely like the Drawer Unity has provided.

    Another workaround could be that I Deserialize the strings of these Conditions and then (somehow) force the deserialized objects to serialize and draw those, but I don't think that's really possible.
    Would be great if I could though, since then I won't have to account for drawing everything else that can be within these Conditions (floats, ints, enums, strings, complex types, ....)
     
  15. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Wow, that sounds pretty tough. I'm not sure how to help after hearing that :(
     
  16. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    I don't quite understand what you seem to have a problem with, but do check SerializeReference, in case that helps you. Unfortunately, that is available only in 2019.3

    Link to a forum post: https://forum.unity.com/threads/serializereference-attribute.678868/
     
  17. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    I did some more digging yesterday and came upon another thread both you and m3rt32 are active in, and m3rt posted a little thing that might prove invaluable there! Basically the approach I theorised of serializing fake objects and having the inspector draw those seems to work out with their little code snippet! Like you reported there though, it doesn't quite work yet for AssetReferences, but I have a hunch on why that is and will experiment with it further.

    Huh, that's an interesting new feature that looks severely under-reported! Something I definitely might want to experiment with, although judging from some comments in that thread it's pretty unstable at the moment.
     
  18. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    I did some quick playing with it and this seems very promising! It fits my use case to a tee, so that's great!
    Unfortunately, on the editor side things aren't exactly up to snuff yet. While I can now serialize arrays of abstract values, the editor does not provide any controls to do anything with these so far and simply draws empty elements (since they're null).
    Here's where it gets tricky: I reasoned I could create a CustomPropertyDrawer either for the Abstract Type or for the Attribute. the latter proved a no-show, but the former works.
    Thus I got the idea to write a PropertyDrawer that checks whether the serialized reference is null, and if so draw a "subclass selector" to store a subclassed object into the element.
    This is where Unity rears its ugly head again though, the SerializedProperty API does not allow to get or set system.object or any complex type values from a SerializedProperty. Only UnityEngine.Object is allowed, which is exactly one of the things the SerializeReference Attribute does not work on.
    Instead I tried to get the Type of the property, reasoning that I can always check if that's the abstract type or something else and if abstract, still draw that subclass selector. Unfortunately the type I get back is a generic ManagedReference, something I can't do anything with...
    SO close, yet so far! ;)
     
  19. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    Perhaps this will help, it was provided by Unity Answers as well as my own research. It allows for getting a field value through a property. If it's a reference type, such as List<T>, you can also modify it. Here's my post.

    https://forum.unity.com/threads/extension-for-serializedproperty.846883/
     
    Last edited: Mar 19, 2020
  20. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Thanks for the heads-up, but in the meantime I got a nice solution myself and I'm working out a few last kinks to really get this system working. I'll share it all soon! :)
     
  21. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    So, as I said before I worked more on this and actually got a system working that plays nicely with the SerializeReference Attribute! I'll provide the code below, together with some examples!
    First of all the example classes. Simply add a component of ComponentWithAttribute somewhere to test this!
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.AddressableAssets;
    5.  
    6. public class ComponentWithAttribute : MonoBehaviour
    7. {
    8.     [SerializeReference]
    9.     public Wild2 wild2Class = new Wild2();
    10.     [SerializeReference]
    11.     public Wild4 wild4Class = new Wild4();
    12.     [SerializeReference]
    13.     public Wild5 wild5Class = new Wild5();
    14.     [SerializeReference]
    15.     public AbstractWild singleClass;
    16.     [SerializeReference]
    17.     public List<AbstractWild> classes;
    18. }
    19.  
    20. [Serializable]
    21. public abstract class AbstractWild
    22. {
    23.     public AbstractWild()
    24.     { }
    25. }
    26.  
    27. [Serializable]
    28. public class Wild1 : AbstractWild
    29. {
    30.     public int number;
    31.  
    32.     public Wild1()
    33.     { }
    34. }
    35.  
    36. [Serializable]
    37. public class Wild2 : AbstractWild
    38. {
    39.     public int notitle;
    40.     public string[] hello;
    41.     public string hello2;
    42.  
    43.     public Wild2()
    44.     { }
    45. }
    46.  
    47. [Serializable]
    48. public class Wild3 : AbstractWild
    49. {
    50.     [SerializeReference]
    51.     public Wild2 wild2Reference;
    52.  
    53.     public Wild3()
    54.     {
    55.         wild2Reference = new Wild2();
    56.     }
    57. }
    58.  
    59. [Serializable]
    60. public class Wild4 : AbstractWild
    61. {
    62.     public AssetReference reference;
    63.  
    64.     public Wild4()
    65.     { }
    66. }
    67.  
    68. public class Wild5 : Wild2
    69. {
    70.     public int notitle2;
    71.  
    72.     public Wild5()
    73.     { }
    74. }
    Next I wrote a little CustomPropertyDrawer of type AbstractWild:
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. [CustomPropertyDrawer(typeof(AbstractWild))]
    7. public class WildDrawer : PropertyDrawer
    8. {
    9.     private List<Type> _inheritedTypes;
    10.     private GUIContent[] _displayOptions;
    11.  
    12.  
    13.     public WildDrawer()
    14.     {
    15.         PropertyClassSelector.GetDropdownOptions(typeof(AbstractWild), out _inheritedTypes, out _displayOptions);
    16.     }
    17.  
    18.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    19.     {
    20.         PropertyClassSelector.DrawClassChangeableProperty(position, property, label, _inheritedTypes, _displayOptions);
    21.     }
    22.  
    23.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    24.     {
    25.         return EditorGUI.GetPropertyHeight(property);
    26.     }
    27. }
    And here's the big one! This class draws the actual control as well as provide the necessary utilities for it.
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Reflection;
    5. using UnityEditor;
    6. using UnityEngine;
    7.  
    8. static public class PropertyClassSelector
    9. {
    10.     /// <summary>
    11.     /// Draws a property with a dropdown to change the assigned object with inheritance.
    12.     /// </summary>
    13.     /// <param name="position">Rect to draw the control in.</param>
    14.     /// <param name="property">Serialized property to draw and control.</param>
    15.     /// <param name="label">Label to draw in front of the dropdown.</param>
    16.     /// <param name="assignableTypes">List of types that can be assigned to the property. Make sure these are all compatible with the underlying type and contains the underlying type!</param>
    17.     /// <param name="displayOptions">Array of type names to display in the dropdown. Make sure these are in sync with assignableTypes!</param>
    18.     static public void DrawClassChangeableProperty(Rect position, SerializedProperty property, GUIContent label, List<Type> assignableTypes, GUIContent[] displayOptions)
    19.     {
    20.         Type type = GetFieldType(property);
    21.         EditorGUI.BeginProperty(position, label, property);
    22.         if (type.IsAbstract) //Draw a dropdown to select a subclass
    23.         {
    24.             Draw(position, property, label, assignableTypes, displayOptions);
    25.         }
    26.         else //Draw inherited class' property drawer
    27.         {
    28.             Rect labelPosition = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
    29.             GetDropdownOptions(GetFieldType(property, true), out List<Type> filteredTypes, out GUIContent[] filteredDisplay);
    30.             Draw(labelPosition, property, label, filteredTypes, filteredDisplay); //Do not give the fixed options here because the field type might be a derived type!
    31.             EditorGUI.PropertyField(position, property, GUIContent.none, true); //Class selector already draws the label, so don't draw it again
    32.         }
    33.         EditorGUI.EndProperty();
    34.     }
    35.  
    36.     /// <summary>
    37.     /// Finds all the classes derived from baseType and provides collections to draw a ClassSelectorDropdown with.
    38.     /// </summary>
    39.     /// <param name="baseType">Base Type that will be used to find all inherited types from.</param>
    40.     /// <param name="types">All the types in the same assembly as baseType, including it.</param>
    41.     /// <param name="displayOptions">GUIContent array in sync with types, listing all class names.</param>
    42.     static public void GetDropdownOptions(Type baseType, out List<Type> types, out GUIContent[] displayOptions)
    43.     {
    44.         types = Assembly.GetAssembly(baseType).GetTypes().
    45.             Where(t => t.IsSubclassOf(baseType) && !t.IsAbstract).ToList();
    46.         types.Insert(0, baseType);
    47.         displayOptions = types.Select((t => new GUIContent(t.Name))).ToArray();
    48.     }
    49.  
    50.     /// <summary>
    51.     /// Draws a dropdown with a given list of selectable classes and assigns the chosen class to property.
    52.     /// </summary>
    53.     /// <param name="position">Rect to draw the control in.</param>
    54.     /// <param name="property">Serialized property to draw and control.</param>
    55.     /// <param name="label">Label to draw in front of the dropdown.</param>
    56.     /// <param name="assignableTypes">List of types that can be assigned to the property. Make sure these are all compatible with the underlying type and contains the underlying type!</param>
    57.     /// <param name="displayOptions">Array of type names to display in the dropdown. Make sure these are in sync with assignableTypes!</param>
    58.     static public void Draw(Rect position, SerializedProperty property, GUIContent label, List<Type> assignableTypes, GUIContent[] displayOptions)
    59.     {
    60.         Rect restRect = EditorGUI.PrefixLabel(position, label);
    61.         int originalIndex = assignableTypes.IndexOf(GetFieldType(property));
    62.         int selectedIndex = EditorGUI.Popup(restRect, originalIndex, displayOptions);
    63.         if (selectedIndex != originalIndex)
    64.         {
    65.             Type newType = assignableTypes[selectedIndex];
    66.             property.managedReferenceValue = newType.IsAbstract ? null : Activator.CreateInstance(newType);
    67.         }
    68.     }
    69.  
    70.     /// <summary>
    71.     /// Returns the type of the Serialized Property.
    72.     /// </summary>
    73.     /// <param name="forceDeclaredFieldType">If true, will return the value declared in script.</param>
    74.     static public Type GetFieldType(SerializedProperty property, bool forceDeclaredFieldType = false)
    75.     {
    76.         if(property.propertyType != SerializedPropertyType.ManagedReference)
    77.         {
    78.             return Type.GetType(property.type);
    79.         }
    80.         string fieldTypeName = property.managedReferenceFullTypename;
    81.         if (string.IsNullOrEmpty(fieldTypeName) || forceDeclaredFieldType)
    82.         {
    83.             fieldTypeName = property.managedReferenceFieldTypename;
    84.         }
    85.         string[] split = fieldTypeName.Split(' ');
    86.         string typeName = split[split.Length - 1] + ", " + split[0]; //Format to "[TYPE], [ASSEMBLY]" for a proper search. Why does Unity format it as "[ASSEMBLY] [TYPE]"?
    87.         return Type.GetType(typeName);
    88.     }
    89. }
    The result looks like this!

    The resulting editor does feel a bit slow, which might be due to all the Type lookups going on since not much of this gets cached.
    Note that each class you can fill in here HAS to have a default constructor for this to work, since whenever you change the dropdown it calls Activator.CreateInstance of the desired class.
    Undo and Redo operations are not supported with this unfortunately, that's apparently an existing issue with SerializeReference fields.
    Another annoying limitation is that the lovely class dropdown you get there will disappear if you make another Property Drawer for one of the types you're drawing here. So if for example you make a Drawer for Wild1 and then have a Wild1 class in your list, the dropdown will disappear for that entry.
    Interestingly enough, since Unity copies array elements when you resize serialized arrays, added elements will copy over the previous entry and stay in sync with them until you force them to make a new object (by changing the type and changing it back).
    Whew, this was a massive one :)
     
    joaoborks, Dunk86 and TheHeftyCoder like this.
  22. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    Yep, I've experimented a bit myself with these things. They are references, hence when you're adding to an array Unity copies the reference from the previous element.

    Also, using reflection for that is one of the classic things to do and works quite nicely. As for the limitation, I believe you can simply make a BaseDrawer for these type of classes that always draws the dropdown at the top. Quite an easy solution.
     
  23. ecurtz

    ecurtz

    Joined:
    May 13, 2009
    Posts:
    640
    I looked at what the PropertyDrawer code was doing and have a hack for getting around it trashing / requiring a label.
    Code (CSharp):
    1. // As of Addressables 1.5.1 the custom property drawer for AssetReference
    2. // destroys the GUIContent passed to it rather than using it for the label
    3. // so we need to make sure it can't ruin GUIContent.none or draw the label
    4. EditorGUIUtility.labelWidth = 0.1f;
    5. GUIContent emptyContent = new GUIContent(GUIContent.none);
    6. EditorGUI.PropertyField(rect, element, emptyContent);
    7. EditorGUIUtility.labelWidth = 0f;
     
  24. travlake

    travlake

    Joined:
    Oct 4, 2019
    Posts:
    50
    +1 for fixing this. Among other things, I'm pretty sure it means tools like Odin can't modify the inspector labels for Addressables.AssetReferences
     
    davidrochin likes this.
  25. davidrochin

    davidrochin

    Joined:
    Dec 17, 2015
    Posts:
    72
    Seriously?...
     
    Silly_Rollo and Alvarezmd90 like this.
  26. Alvarezmd90

    Alvarezmd90

    Joined:
    Jul 21, 2016
    Posts:
    151
    I tried today, for the first time diving into property drawers. Man what a nightmare. Most annoying thing I've tried so far in Unity. Brings back awful memories of me trying html in notepad for school. Trying to get the margins and origins right for hours on end without avail.
     
    Last edited: Apr 16, 2020
  27. Silly_Rollo

    Silly_Rollo

    Joined:
    Dec 21, 2012
    Posts:
    501
    Aren't Addressables supposed to be out of preview? This is a pretty glaring omission
     
    mckconor, Frizzil, jimmack and 3 others like this.
  28. Ziflin

    Ziflin

    Joined:
    Mar 12, 2013
    Posts:
    132
    @Yandalf Just found your post for some reason, but I finished implementing a similar property drawer for this today as well. I'm caching the types & typenames and using Unity's TypeCache to reduce garbage generation and make it as fast as I could. Mine's working for custom type-based property drawers (Unity really needs to at least expose ScriptAttributeUtility.GetDrawerTypeForType(), if not provide a better way to continue the iteration of attribute-based and type-based custom property drawers). Also:

    I will PAY $$ for a Unity UI developer to fix the current behavior of how creating the first element of an array works and how adding new ones work. For the love of everything, please use Activator.CreateInstance() to create the first entry (if not all). Currently adding the 1st entry does not call the constructor for normal C# classes (it does for ones with [SerializedReference] and for non-array fields) - so please make this consistent.

    The other issue with arrays (was mentioned above) is that it currently copies the previous entry when adding a new one. This is certainly not what most people want when using [SerializedReference] as new entries reference the same object. Most people will think it made a copy (since the normal one does) and likely not realize that they are modifying the previous entry as well.

    Seriously, there's cash-money involved if I can get someone to fix these annoying/error-prone array issues :)
     
    Reahreic likes this.
  29. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Using property fields and serializedObject should generally be the norm in custom inspectors anyway.
     
  30. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    FYI this immediately broke for me on a simple test case :).

    Your code requires ALL places where AbstractWild exists *anywhere* in the codebase to be marked SerializeReference. Any place where that isn't true: your "type" var will eval to null - so all the code that references "type" needs to be doing null-checks and rendering appropriately without it.
     
  31. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Not even! I have HUUUUGE inspectors in all our plugins. Talking 5000 lines of code in some even with shared helper methods. But we only use serializedObject where no option is available (Asset References) because it's just dangerous code and can break easily even though it compiles.

    I wonder where your thought is coming from though. Do provide more details if you feel inclined.
     
    Frizzil and davidrochin like this.
  32. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    I am aware, been using MasterAudio for 7 years now. ;-)

    Inspectors that look and feel like the Unity inspector, with robust multi selection, Undo, etc. use the Unity inspector API, which is SerializedObject/SerializedProperty. Anything else results in something that looks, acts and feels alien to the rest of the inspector. That is fine for something like MasterAudio where the workflow is absolutely anything but the standard Unity inspector, it is its own thing, but not at all for regular inspectors.

    Plus, the amount of legwork you had to do with your helpers for things to work properly, versus what Unity already does by default is quite a thing.

    I have rewritten the custom inspectors for quite a lot of third party assets we have bought in because of this, they always have *some* weirdness going on that results in either unintuitive results when multi selecting, no multi selection, undo breaking and undoing past where expected, redo corrupting data, custom property drawers being ignored, etc. All of these are literally not an issue when using SerializedProperty it all just works by default, which it would as it is *the* way Unity inspectors are written internally, default or specialised. If you are worried about the string nature of it, just use the nameof() operator, although the reality is that Unity serialization is string based so it's the serializer that can be dangerous, not its external tooling API.

    I have written tonnes of custom tooling over the years too, and for a long time now a fully custom inspector has been a last resort. I always go for CustomPropertyDrawers first, then partial custom editors, then custom editors. We have helpers that draw all properties except some, or until some, or from some, allowing inserting custom stuff in particular places, custom controls and anything manipulating data will always go through the serializer API.

    The only truly custom drawing we use (although it still looks and feels the same as Unity's inspector) is a Json-backed fully polymorphic property drawer for arbitrary data classes with full array etc support that I developed over the past couple of projects, and that is still always backed by SerializedProperty. We have that as a CustomPropertyDrawer too, as well as something a custom inspector can use.

    You have done an amazing job with the MasterAudio tooling. In general, most custom inspectors asset writers make are nowhere near as robust and well developed. Unity has a standard way of writing inspectors that should be used where possible and especially where people have neither the time nor knowledge that you have to go the extra mile and get things behaving. That way of approaching this results in inspectors that work correctly by default, with a full feature set. They also work with all Unity features introduced even after they are written. New Prefab workflows? Presets? Multiple scene workflows? Copy and paste? Arrays reorderable by default? New styles? No changes required at all. Most of my tooling for our games targetting 5.x and even quite a lot targetting 4.x (apart from what was written before I "saw the light") still works on 2020.x.
     
    Last edited: Aug 4, 2020
    orionsyndrome likes this.
  33. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    The irony is that the SerializedProperty way is so poorly designed/architected that it requries a lot more code to achieve the same core functionality and the win doesn't come until/unless you need a lot of complexity, that code is much harder to read and debug (API is full of misnamed variables and counter-intuitive object relationships), and for small cases is often more boiler-plate than functional. I wish Serialized* was usable, but it never has been, it's always been painful to work with. And let's not get started on how it interacts with UnityEvent...

    My hopes are on UIToolkit and whatever it evolves into now, to save us from all of this :).

    (I've been writing custom inspectors and editorwindows since 2013. These days I start with a custom inspector for almost everything because its SO MUCH faster to write, work with, prototype etc ... and eventually upgrade things to Serialized* if and only if there's a compelling need - usually forced to because of missing / buggy Unity features that only work that way)

    (PS: Undo generally isn't supported or fulfilled even by Unity themselves - I logged a bug against this only a month ago that shows even Unity has still been writing non-Undoable code on their classes. If Unity lived up to the "everything must support Undo" claim, if they'd ever fulfilled it themselves(!) or had ever blocked assets from asset store (always claimed, never done - because Unity's own Undo has always been broken), then SerializedProperty etc would look more attractive to a lot more people I think)
     
  34. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Don't get me wrong, there is nothing particularly nice about the API, but if you want things to behave the same as Unity's stuff that is the only way to do it and the way it should be done. Even if you disagree with how standard Unity inspectors work, using the same code results in any changes, fixes etc. that Unity makes apply to your code to. and any ingrained user behaviours remain satisfied. I have lost count of how many times I've had designers come to me to ask why a particular inspector doesn't behave the same as everything else, breaking their workflow.

    I don't find it slower to write at all these days, but that could be because we both have a large library of helpers developed over the years and a lot of acquired knowledge on how it works.

    Unity's worst enemy in this is the documentation. The best documentation on how to make an inspector that feels "familiar" has always been ILSpy. Until the reference source was released at least. :p

    That, I completely agree with. Regardless of anything else, laying anything out in absolutely ANY non-default way with IMGUI is akin to dragging your face across broken glass. Repeatedly.

    Same, been writing tooling since 2013 too but I have gone in entirely the opposite direction and these days I try my absolute best not to write a custom inspector and even if I do end up doing it will use SerializedProperties and CustomPropertyDrawers (which also makes any custom controls you develop transferable to any inspector).

    Undo not working is always a bug. The problem is that I feel that even newer Unity employees are not properly brought up to speed there.
     
    CodeRonnie and orionsyndrome like this.
  35. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Yeah, agreed - a few years ago I started deleting as much as I could of the library code, because it's just extra tech that has to be maintained (and is horrible to maintain). That's pushed me more towards YAGNI and "if you just need a custom inspector, write it in the fewest lines possible - only do the complex Serialized* version if/when you have evidence it''s going to be needed long-term (e.g. if you convert a one-off feature into something you want to re-use widely, sell on asset store, etc)".

    :D

    I've been (successfully) logging bugs against Undo for 5+ years now ... I mostly wait 6-18 months to see if Unity notices that "core feature that absoutely 100% cannot be working and Unity CANNOT be testing it and CANNOT be using Undo in their own APIs and projects and not have noticed this bug" ... and then eventually log it and see how quickly they fix it (usually: very quick). It's a fun experiment, and just proves over and over and over again that Unity staff don't consider Undo important or relevant (unlike everyone who actually makes games).
     
  36. jerotas

    jerotas

    Joined:
    Sep 4, 2011
    Posts:
    5,572
    Ah ok. I do want to note that every action possible in our custom inspectors does have full undo. The Unity API does provide that. I also do use the nameof operator as you suggest.

    Basically the reason I think I had to kinda roll my own is all the 2 to 3 level deep nested sections of controls, for that to appear connected not explode your brain.

    Custom Property Drawers I don't really know how to do. I would be a big task to rewrite everything for sure.
     
  37. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    491
    Thanks for pointing that out. I'll take a look at it again.
    Annoying...
     
  38. Ragueel

    Ragueel

    Joined:
    Jun 2, 2018
    Posts:
    39
    So I did serialization of the asset reference by creating a scriptable object and using
    ObjectField
    in the inspector. Don't know if it is a good solution but it worked for me. Something like:
    Code (CSharp):
    1. [CreateAssetMenu(menuName = "SO/Variables/AssetReference", order = 0)]
    2.     public class AssetReferenceSO : ScriptableObject
    3.     {
    4.         [SerializeField] private AssetReference _assetReference;
    5.  
    6.  
    7.         public AssetReference Reference => _assetReference;
    8.     }
     
  39. david-wtf

    david-wtf

    Joined:
    Sep 30, 2021
    Posts:
    25
    styGGx likes this.
  40. leandro_nw

    leandro_nw

    Joined:
    Jul 17, 2012
    Posts:
    15
    This saved my day