Search Unity

Full Inspector: Inspector and serialization for structs, dicts, generics, interfaces

Discussion in 'Assets and Asset Store' started by sient, Jan 23, 2014.

  1. jrhee

    jrhee

    Joined:
    Dec 5, 2013
    Posts:
    74
    I have some BaseBehavior prefabs that contain custom data classes, and noticed the serialization behavior is different from Monobehaviour. For example, if I have a prefab that contains something like:

    Code (csharp):
    1.  
    2. public class Character : BaseBehavior
    3. {
    4.     public Stats stats;
    5. }
    6. ...
    7. [Serializable]
    8. public class Stats
    9. {
    10.     public int hp;
    11.     public int damage;
    12. }
    13.  
    With Monobehaviour, if I change the hp value on the prefab, damage is unaffected on any instances.

    With BaseBehavior, if I change the hp value on the prefab, the serialized data for the entire Stats class is affected on any instances.

    This makes using BaseBehavior with prefabs almost impossible with complex, nested data. Is this expected behavior, and is there a workaround to make it behave like MonoBehaviour?
     
  2. alpha-cast

    alpha-cast

    Joined:
    Nov 18, 2017
    Posts:
    2
    I just encountered an error using a char with Full Inspector. Does it not support chars?

    It's on a property defined like so:
    Code (CSharp):
    1. [SerializeField]
    2. public SharedInstance<List<Pair<char, BlockKind>>> BlockMappings;
    Where `BlockKind` is an empty class that inherits from `BaseScriptableObject` and `Pair` is a `[Serializable] struct` containing nothing but 2 fields.

    Here's the exception:
    Code (CSharp):
    1. Exception caught when deserializing property <Instance> with type <FullInspector.Generated.SharedInstance.SharedInstance_SystemCollectionsGenericSystem_Collections_Generic_List_Pair_System_Char_BlockKind__>
    2. System.ArgumentException: Object of type 'System.String' cannot be converted to type 'System.Char'.
    3.   at System.RuntimeType.CheckValue (System.Object value, System.Reflection.Binder binder, System.Globalization.CultureInfo culture, System.Reflection.BindingFlags invokeAttr) [0x00071] in <c95265f74fdf4905bfb0d5a4b652216c>:0
    4.   at System.Reflection.MonoField.SetValue (System.Object obj, System.Object val, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Globalization.CultureInfo culture) [0x0007d] in <c95265f74fdf4905bfb0d5a4b652216c>:0
    5.   at System.Reflection.FieldInfo.SetValue (System.Object obj, System.Object value) [0x00000] in <c95265f74fdf4905bfb0d5a4b652216c>:0
    6.   at FullSerializer.Internal.fsMetaProperty.Write (System.Object context, System.Object value) [0x00029] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\Reflection\fsMetaProperty.cs:117
    7.   at FullSerializer.Internal.fsReflectedConverter.TryDeserialize (FullSerializer.fsData data, System.Object& instance, System.Type storageType) [0x000df] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\Converters\fsReflectedConverter.cs:81
    8.   at FullSerializer.fsSerializer.InternalDeserialize_5_Converter (System.Type overrideConverterType, FullSerializer.fsData data, System.Type resultType, System.Object& result) [0x0002c] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:836
    9.   at FullSerializer.fsSerializer.InternalDeserialize_4_Cycles (System.Type overrideConverterType, FullSerializer.fsData data, System.Type resultType, System.Object& result) [0x0003e] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:828
    10.   at FullSerializer.fsSerializer.InternalDeserialize_3_Inheritance (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0012a] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:807
    11.   at FullSerializer.fsSerializer.InternalDeserialize_2_Version (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0015a] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:747
    12.   at FullSerializer.fsSerializer.InternalDeserialize_1_CycleReference (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0005b] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:702
    13.   at FullSerializer.fsSerializer.TryDeserialize (FullSerializer.fsData data, System.Type storageType, System.Type overrideConverterType, System.Object& result) [0x00050] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:673
    14.   at FullSerializer.fsSerializer.TryDeserialize (FullSerializer.fsData data, System.Type storageType, System.Object& result) [0x00006] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:648
    15.   at FullSerializer.Internal.fsIEnumerableConverter.TryDeserialize (FullSerializer.fsData data, System.Object& instance_, System.Type storageType) [0x000d7] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\Converters\fsIEnumerableConverter.cs:78
    16.   at FullSerializer.fsSerializer.InternalDeserialize_5_Converter (System.Type overrideConverterType, FullSerializer.fsData data, System.Type resultType, System.Object& result) [0x0002c] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:836
    17.   at FullSerializer.fsSerializer.InternalDeserialize_4_Cycles (System.Type overrideConverterType, FullSerializer.fsData data, System.Type resultType, System.Object& result) [0x0003e] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:828
    18.   at FullSerializer.fsSerializer.InternalDeserialize_3_Inheritance (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0012a] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:807
    19.   at FullSerializer.fsSerializer.InternalDeserialize_2_Version (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0015a] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:747
    20.   at FullSerializer.fsSerializer.InternalDeserialize_1_CycleReference (System.Type overrideConverterType, FullSerializer.fsData data, System.Type storageType, System.Object& result, System.Collections.Generic.List`1[FullSerializer.fsObjectProcessor]& processors) [0x0005b] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:702
    21.   at FullSerializer.fsSerializer.TryDeserialize (FullSerializer.fsData data, System.Type storageType, System.Type overrideConverterType, System.Object& result) [0x00050] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:673
    22.   at FullSerializer.fsSerializer.TryDeserialize (FullSerializer.fsData data, System.Type storageType, System.Object& result) [0x00006] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\Module\Assets\FullSerializer\Source\fsSerializer.cs:648
    23.   at FullInspector.FullSerializerSerializer.Deserialize (System.Reflection.MemberInfo storageType, System.String serializedState, FullInspector.ISerializationOperator serializationOperator) [0x0003c] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\FullSerializer\FullSerializerSerializer.cs:98
    24.   at FullInspector.Internal.fiISerializedObjectUtility.RestoreState[TSerializer] (FullInspector.ISerializedObject obj) [0x001bf] in C:\Users\fhros\Source\RFPS\Assets\FullInspector2\Core\fiISerializedObjectUtility.cs:224
    25. UnityEngine.Debug:LogError(Object, Object)
    26. FullInspector.Internal.fiISerializedObjectUtility:RestoreState(ISerializedObject) (at Assets/FullInspector2/Core/fiISerializedObjectUtility.cs:229)
    27. FullInspector.BaseScriptableObject`1:RestoreState() (at Assets/FullInspector2/Core/BaseScriptableObject.cs:59)
    28. FullInspector.Internal.fiSerializationManager:DoDeserialize(ISerializedObject) (at Assets/FullInspector2/Core/fiSerializationManager.cs:210)
    29. FullInspector.Internal.fiSerializationManager:OnUnityObjectDeserialize(ISerializedObject) (at Assets/FullInspector2/Core/fiSerializationManager.cs:117)
    30. FullInspector.BaseScriptableObject`1:UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize() (at Assets/FullInspector2/Core/BaseScriptableObject.cs:119)
     
  3. Ghopper21

    Ghopper21

    Joined:
    Aug 24, 2012
    Posts:
    170
    We've been using FullInspector with success -- great UX. However, in looking into why our compile times are so long, we suspect that FullInspector may be at least partly to blame. We are looking further, but I wanted to ask here: is it known whether FullInspector adds to compile times?
     
  4. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ghopper21 One thing to try, is to use FullInspector in DLL form.

    FullInspector itself shouldn't add much to compilation time, but it surely adds some non-zero time to it.
     
  5. Ghopper21

    Ghopper21

    Joined:
    Aug 24, 2012
    Posts:
    170
    @SugoiDev -- thanks, yeah I'm not worried about the time to compile FullInspector itself (though yet using the DLL is a good tip), my question is about whether someone FullInspector causes compile time to be longer for the rest of the project somehow. We thought for instance that FI was triggering a double compile for the project on any code change, but we're not sure.
     
  6. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ghopper21 It does a few things that might increase the assembly reload time. For example, heavy LINQ use to construct models of the loaded assemblies (for caching). This is that pause right after compilation finishes, when Unity seems to hang for a few seconds.

    I've never seen it triggering a double compilation (or double reload), but you can do some testing.

    Try using this class and check out what it says. Put it in a Editor folder, if you're not using custom assemblies.

    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. [UnityEditor.InitializeOnLoad]
    6. public sealed class CompilationNotifier {
    7.  
    8.     static CompilationNotifier() {
    9.         UnityEditor.EditorApplication.update -= CompilationNotifier.OnEditorUpdate;
    10.         UnityEditor.EditorApplication.update += CompilationNotifier.OnEditorUpdate;
    11.  
    12.         UnityEditor.AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReloadHandler;
    13.         UnityEditor.AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReloadHandler;
    14.  
    15.         UnityEditor.AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReloadHandler;
    16.         UnityEditor.AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReloadHandler;
    17.     }
    18.  
    19.  
    20.     private static bool StoredCompilingState {
    21.         get { return UnityEditor.EditorPrefs.GetBool("CompilationNotifier_StoredCompilingState"); }
    22.         set { UnityEditor.EditorPrefs.SetBool("CompilationNotifier_StoredCompilingState", value); }
    23.     }
    24.  
    25.  
    26.     private static void OnBeforeAssemblyReloadHandler() {
    27.         Debug.LogFormat("{0:h:mm:ss.fff} Assembly reload begin", DateTime.Now);
    28.         OnCompilationFinishedHandler();
    29.     }
    30.  
    31.  
    32.     private static void OnAfterAssemblyReloadHandler() {
    33.         Debug.LogFormat("{0:h:mm:ss.fff} Assembly reload finished", DateTime.Now);
    34.     }
    35.  
    36.  
    37.     private static void OnCompilationFinishedHandler() {
    38.         if (CompilationNotifier.StoredCompilingState) {
    39.             CompilationNotifier.StoredCompilingState = false;
    40.             Debug.LogFormat("{0:h:mm:ss.fff} Compilation has finished", DateTime.Now);
    41.         }
    42.     }
    43.  
    44.  
    45.     [UnityEditor.Callbacks.DidReloadScripts]
    46.     private static void OnDidReloadScripts() {
    47.         OnCompilationFinishedHandler();
    48.     }
    49.  
    50.  
    51.     private static void OnEditorUpdate() {
    52.         if (UnityEditor.EditorApplication.isCompiling && !CompilationNotifier.StoredCompilingState) {
    53.             CompilationNotifier.StoredCompilingState = true;
    54.             Debug.LogFormat("{0:h:mm:ss.fff} Compilation has began", DateTime.Now);
    55.         }
    56.     }
    57.  
    58. }
    59.  
     
  7. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Hello,
    I have a problem with my own implementation and I would greatly appreciate some help.

    I digged through the implementation of Facade<T> as I was interested in the dropdown of Types, with the aim to create a dropdown which would allow me to choose from types which are derived from a certain type, similar what Facade<T> is doing, however, without the need/use of creating an instance, so for type-based selection only.

    Similar to Facade<T>, my class looks like this:
    Code (CSharp):
    1.  
    2. [Serializable]
    3. public class TypeSelection<T>
    4. {
    5.     [SerializeField]
    6.     public Type InstanceType;
    7. }
    8.  
    Simple as it is. If I now use this somewhere, let's say like this:
    Code (CSharp):
    1. public class Modifier : BaseBehavior, IComparable<Modifier>
    2. {
    3.     [SerializeField]
    4.     private TypeSelection<Stat> type;
    5.     // other stuff
    6. }
    7.  
    I would like to see a dropdown in the inspector, allowing me to choose from all types which derive from Stat, in this example.

    So far so good. I did succeed in having the dropdown selection to show up and I can properly select and choose, however, it seems as if I don't properly serialize ´TypeSelection<T>`, as I get NullReferenceExceptions for the InstanceType property when I try to use it.

    This is my implementation of the PropertyEditor. There are actually two implementations, one trying to copy FacadeEditor<T> (commented out) and one using TypeDropdownOptionsManager. The latter one is by far more easier to understand. Both appear to be working, but I get NullReferenceExceptions when trying to use InstanceType of TypeSelection<T>.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using FullInspector;
    4. using FullInspector.Internal;
    5.  
    6. [CustomPropertyEditor(typeof(TypeSelection<>))]
    7. public class TypeSelectionEditor<T> : PropertyEditor<TypeSelection<T>>
    8. {
    9.     public override TypeSelection<T> Edit(Rect region, GUIContent label, TypeSelection<T> element, fiGraphMetadata metadata)
    10.     {
    11.         var manager = new TypeDropdownOptionsManager(typeof(T), true);
    12.  
    13.         if (element == null)
    14.         {
    15.             element = new TypeSelection<T>();
    16.         }
    17.  
    18.         if (element.InstanceType == null)
    19.         {
    20.             element.InstanceType = manager.GetTypeForIndex(0, element.InstanceType);
    21.         }
    22.         element.InstanceType = manager.GetTypeForIndex(EditorGUI.Popup(region, manager.GetIndexForType(element.InstanceType), manager.GetDisplayOptions()), element.InstanceType);
    23.  
    24.         return element;
    25.     }
    26.     public override float GetElementHeight(GUIContent label, TypeSelection<T> element, fiGraphMetadata metadata)
    27.     {
    28.         return EditorStyles.numberField.CalcHeight(label, 1000);
    29.     }
    30.  
    31.     //private static FacadeTypeManager TypeOptions = new FacadeTypeManager(typeof(T));
    32.     //private static float LabelHeight = EditorStyles.label.CalcHeight(GUIContent.none, 100);
    33.     //private const float SplitterHeight = 2f;
    34.  
    35.     //private static void DrawHeader(ref Rect region, GUIContent label, TypeSelection<T> element)
    36.     //{
    37.     //    if (string.IsNullOrEmpty(label.text) && TypeOptions.Types.Length == 1)
    38.     //    {
    39.     //        return;
    40.     //    }
    41.  
    42.     //    Rect labelRect = region;
    43.     //    labelRect.height = LabelHeight;
    44.     //    region.y += LabelHeight;
    45.     //    region = fiRectUtility.IndentedRect(region);
    46.  
    47.     //    if (TypeOptions.Types.Length == 1)
    48.     //    {
    49.     //        EditorGUI.LabelField(labelRect, label);
    50.     //    }
    51.     //    else
    52.     //    {
    53.     //        int currentIndex = TypeOptions.GetDisplayOptionIndex(element.InstanceType);
    54.     //        int updatedIndex = EditorGUI.Popup(labelRect, label, currentIndex, TypeOptions.DisplayedOptions);
    55.  
    56.     //        if (currentIndex != updatedIndex &&
    57.     //            updatedIndex >= 0 && updatedIndex < TypeOptions.Types.Length)
    58.     //        {
    59.     //            element.InstanceType = TypeOptions.Types[updatedIndex];
    60.     //        }
    61.     //    }
    62.     //}
    63.  
    64.     //public override TypeSelection<T> Edit(Rect region, GUIContent label, TypeSelection<T> element, fiGraphMetadata metadata)
    65.     //{
    66.     //    if (element == null)
    67.     //    {
    68.     //        element = new TypeSelection<T>();
    69.     //    }
    70.  
    71.     //    if (element.InstanceType == null)
    72.     //    {
    73.     //        element.InstanceType = TypeOptions.Types[0];
    74.     //    }
    75.  
    76.     //    DrawHeader(ref region, label, element);
    77.  
    78.     //    return element;
    79.     //}
    80.     //public override float GetElementHeight(GUIContent label, TypeSelection<T> element, fiGraphMetadata metadata)
    81.     //{
    82.     //    return EditorStyles.numberField.CalcHeight(label, 1000);
    83.     //}
    84. }
    One can serialize Type, can't one? Could you help me to find my mistake, please?
     
  8. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    About the serialization issue, FullSerializer (default serializer on FullInspector) does support serializing a Type (see here). Are you inheriting from BaseBehaviour/BaseObject/BaseScriptableObject?


    About the selector: did you try the
    TypeSpecifier<TBaseType>? I think it does what you coded: it shows a dropdown to select types derived from a given type.
     
    Ardenian likes this.
  9. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    Thank you for your reply, SugoiDev!

    I had another look on it and in short, the problem was related to OnEnable() and populating a Facade<T>, so I would try to get InstanceType while it wasn't populated yet.

    Thank you for your hint, TypeSpecifier<T> is exactly what I am looking for!

    Perhaps you could help me with a question, if you don't mind, please. Noticing TypeSpecifier<T>, I wonder, is there any documentation about the full features? I know about the guide, however, is there a complete list?

    Right now, I search for something like
    Code (CSharp):
    1.  
    2. public class FooClass : BaseBehavior{
    3.  
    4.     [Popup("Alpha", "Beta", "Gamma", "Delta")]
    5.     [SerializeField]
    6.     private string s;
    7. }
    8.  
    Basically showing a popup and then typecasting it to the attribute. Think I can manage to write that myself, I do wonder whether Fullinspector already has such a thing. Do you know by chance if it does?
     
  10. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ardenian

    There's nothing built-in that I know of, but you can create one real fast with the tkEditors.
    I just created this for you to test.
    It's not super tested, but you can play around and modify it to better suit your needs.

    Let me know if it helps!

    Edit: I forgot to reply to your other question, about documentation.
    Man, I with there was.
    A while back I was about to create better docs and some extra extensions, but noticed that the developer was abandoning the project. Sadly, it was indeed abandoned.

    I still think this is the best editor implementation for Unity.
    It's not fancy, but the code is relatively simple and it can be fast enough.
    The lack of more in-depth docs and development is really a pity.


    Note: Just use it like this

    Code (CSharp):
    1. [SerializeField]
    2. private ConstrainedDropdown<string> _stringTestDropdown =
    3.     new ConstrainedDropdown<string>("someLabel", "element1", "element2");
    4.  
    5.  
    6. [SerializeField]
    7. private ConstrainedDropdown<int> _intTestDropdown =
    8.     new ConstrainedDropdown<int>("someLabel", 1, 100);

    Code (CSharp):
    1. namespace FullInspectorExtras {
    2.     using System;
    3.     using System.Linq;
    4.     using FullInspector;
    5.     using UnityEngine;
    6.  
    7.     [Serializable]
    8.     public sealed class ConstrainedDropdown<T>:
    9. #if UNITY_EDITOR
    10.         tkCustomEditor {
    11.  
    12. #else
    13.     {
    14. #endif
    15.  
    16.         [SerializeField]
    17.         public T Value;
    18.  
    19.         [SerializeField]
    20.         private T[] _options;
    21.  
    22.         [SerializeField]
    23.         private string _label;
    24.  
    25.         [SerializeField]
    26.         private int _selectedIndex = -1;
    27.  
    28.         public ConstrainedDropdown() { }
    29.  
    30.  
    31.         public ConstrainedDropdown(string label, params T[] options) {
    32.             _label = label;
    33.             _options = options;
    34.         }
    35.  
    36.  
    37. #if UNITY_EDITOR
    38.         //cache to avoid too many allocations
    39.         private GUIContent[] _optionsLabels;
    40.         /// <inheritdoc />
    41.         public tkControlEditor GetEditor() {
    42.             var group = new tk<ConstrainedDropdown<T>>.VerticalGroup
    43.             {
    44.                 new tk<ConstrainedDropdown<T>>.Label(
    45.                     tk<ConstrainedDropdown<T>>.Val(
    46.                         (o, c) =>
    47.                             new fiGUIContent(!Equals(default(T), o.Value) ? o.Value.ToString() : "NO VALUE")
    48.                     )
    49.                 ),
    50.                 new tk<ConstrainedDropdown<T>>.Popup(
    51.                     tk<ConstrainedDropdown<T>>.Val((o, c) => new fiGUIContent(o._label)),
    52.                     tk<ConstrainedDropdown<T>>.Val(
    53.                         (o, c) => {
    54.                             if (o._optionsLabels == null) {
    55.                                 if (o._options == null) {
    56.                                     return new GUIContent[] { };
    57.                                 }
    58.  
    59.                                 o._optionsLabels = o._options.Select(t => new GUIContent(t.ToString())).ToArray();
    60.                             }
    61.  
    62.                             return o._optionsLabels;
    63.                         }),
    64.                     tk<ConstrainedDropdown<T>>.Val(
    65.                         (o, c) => o._selectedIndex),
    66.                     (o, c, newSelection) => {
    67.                         o._selectedIndex = newSelection;
    68.                         o.Value = o._options != null
    69.                             ? o._options[o._selectedIndex]
    70.                             : default(T);
    71.  
    72.                         return o;
    73.                     })
    74.             };
    75.  
    76.             return new tkControlEditor(group);
    77.         }
    78. #endif
    79.  
    80.     }
    81. }
    Edit: code fixes
    Edit2: added a few null-guards
     
    Last edited: Mar 31, 2018
  11. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @SugoiDev

    Thank you for coming back to me!

    It is a shame this project is/seems to be abandoned. Honestly, I wouldn't use Unity without this kind of feature-richness anymore. I read your posts at GitHub regarding alternatives, there seem to be some interesting alternatives.

    Thank you for your implementation!
    Unfortunately, there seem to be numerous issues, starting with options not being updated when I change them in the code, missing elements from options ( looked at the code, that shouldn't happen?) and some other nasty stuff.
    The Toolkit seems to be a great innovation in comparison to the Editor, yet it is hard to read and for beginners to use, just like the Guide states itself.

    Call me old fashioned, I wrote an implementation with PropertyEditor based on your approach:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Linq;
    4. using FullInspector;
    5. using UnityEngine;
    6.  
    7. [Serializable]
    8. public class ConstrainedDropdown<T>
    9. {
    10.     [SerializeField]
    11.     public T Value;
    12.  
    13.     [SerializeField]
    14.     private T[] _options;
    15.  
    16.     [SerializeField]
    17.     private string _label;
    18.  
    19.     public ConstrainedDropdown(string label, params T[] options)
    20.     {
    21.         _label = label;
    22.         _options = options;
    23.     }
    24.  
    25.     public T[] Options
    26.     {
    27.         get
    28.         {
    29.             return _options;
    30.         }
    31.     }
    32.  
    33.     public string Label
    34.     {
    35.         get
    36.         {
    37.             if (String.IsNullOrEmpty(_label))
    38.             {
    39.                 return "NO_LABEL";
    40.             }
    41.             return _label;
    42.         }
    43.     }
    44. }
    Code (CSharp):
    1. using FullInspector;
    2. using System;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. [CustomPropertyEditor(typeof(ConstrainedDropdown<>))]
    7. public class ConstrainedDropdownPropertyEditor<T> : PropertyEditor<ConstrainedDropdown<T>>
    8. {
    9.     public override ConstrainedDropdown<T> Edit(Rect region, GUIContent label, ConstrainedDropdown<T> element, fiGraphMetadata metadata)
    10.     {
    11.         if(element == null)
    12.         {
    13.             return element;
    14.         }
    15.  
    16.         if (element.Value == null)
    17.         {
    18.             element.Value = element.Options[0];
    19.         }
    20.         element.Value = element.Options[EditorGUI.Popup(region, element.Label, Array.FindIndex(element.Options, s => s.Equals(element.Value)), Array.ConvertAll(element.Options,s => s.ToString()))];
    21.  
    22.         return element;
    23.     }
    24.     public override float GetElementHeight(GUIContent label, ConstrainedDropdown<T> element, fiGraphMetadata metadata)
    25.     {
    26.         return EditorStyles.popup.CalcHeight(label, 1000);
    27.     }
    28. }
    29.  
    This seems to work, there is however an issue with the label, for some reason it isn't present anymore and always ´null ( or empty) when trying to use it in the PropertyDrawer. No clue why, a fast look did not reveal anything to me.

    Thank you for your guidance, @SugoiDev I greatly appreciate it!

    EDIT:

    Okay, seems the problem with the label lies elsewhere, when resetting the BaseBehavior it is shown correctly again. Seems the problem is related to creating the object directly into the attribute, or however one calls that.
     
    Last edited: Mar 31, 2018
  12. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ardenian Awesome you got it working (kinda)!

    Regarding the issues, I updated the original post with a fixed version so future readers can have an alternative (seems to be working now. If you test and find issues, let us know!).


    A very common gotcha with the toolkit is forgetting to use a "fresh" object/context. Those "o." refer to the "this" object.
    The editors are heavy cached per type (for performance), so you need to tell it what object is "this".

    This was the source of many ghost bugs in my usage!
    I was thinking of writing a Roslyn analyzer, but since the project was not active anymore I postponed the idea.
     
  13. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @SugoiDev

    Great to see yours is working as well now! I am going to test it, too.

    Unfortunately, I ran into a problem with my implementation, which is, I believe, related to the usage of it.
    I would like to use it in a Facade<T>, similar to this:

    Code (CSharp):
    1.  
    2. public class ModifierManager : BaseBehavior {
    3.     [SerializeField]
    4.     private List<Facade<Modifier>> modifiers;
    5. }
    6.  
    Code (CSharp):
    1. public class Modifier : BaseBehavior{
    2.     [SerializeField]
    3.     private ConstrainedDropdown<string> identifier = new ConstrainedDropdown<string>("Identifier", "Addition", "Subtraction", "Multiplication", "Division");
    4.     //...
    5. }
    However, it seems that Facade<t> doesn't allow this,
    identifier
    of modifier will always be null and it will not receive the value we assign to it. Doesn't work with adding a constructor and assigning the
    identifier
    as well, despite that constructor getting called at some point.
    Since
    ConstrainedDropdown<T>
    is supposed to already have a value when it is drawn in the inspector, there is no way to add a value to it.

    (See attached image, please)

    Do you know by chance a quick fix to this? I would assume that your solution has the same issue, but I will test it as well.

    EDIT:

    Yes, same problem with your implementation:

    Code (CSharp):
    1. public class Test : BaseBehavior
    2. {
    3.     public List<Facade<Test2>> f;
    4.  
    5. }
    6.  
    7. public class Test2 : BaseBehavior
    8. {
    9.     public ConstrainedDropdown2<int> c = new ConstrainedDropdown2<int>("c1", 1, 2, 3, 4, 5);
    10. }
    Here Test will not show the dropdown, but
    NO VALUE
     

    Attached Files:

    Last edited: Mar 31, 2018
  14. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ardenian I never actually used the Facade. It's too distant from the "normal" workflow and always felt too brittle.
    In your specific case, can't those Modifiers be normal classes?

    If they can, then you don't need the facade. Just use a List<Modifier> and FullInspector will take care of the rest.
     
  15. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @SugoiDev Maybe you are right, I stopped using it. It can be quite useful in some cases, a shame it is limited to BaseBehavior, I think. Something like a Factory<T> could be quite useful in some cases, as you don't have to create T and then copy the attributes of T into the factory, effectively copying them.

    I am still not quite happy with our solutions regarding the dropdown, I would prefer a InspectorDropdownAttribute, I am going to look through the already implemented attributes to see if there is any attribute which modifies its target value, so I could copy that behavior into a dropdown attribute.
     
    SugoiDev likes this.
  16. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @SugoiDev and who else might be interested, here is an implementation using attributes.

    I looked at the implementation of InspectorRangeAttribute, damn, that thing has potential!
    What it basically does, if you use
    AttributePropertyEditor
    , then you get access to not only the attribute itself, but also the instance object that the attribute is targetting, which means, you can alter both to your liking, style it, whatsoever.

    I myself am looking to implement one or another attribute from NaughtyAttributes using FullInspector. They have some neat stuff there which FullInspector hasn't yet, as example the progress bar. If you guys are interested in them, I could post them here.

    Anyways, here is the code for the InspectorDropdownAttribute:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using FullInspector;
    4.  
    5. public class Test : BaseBehavior
    6. {
    7.     [SerializeField]
    8.     [InspectorDropdown("Alpha", "Beta", "Gamma", "Delta")]
    9.     private string myStringDropdown;
    10.  
    11.     [SerializeField]
    12.     [InspectorDropdown(5, 10, 15, 20)]
    13.     private string myIntDropdown;
    14. }
    Code (CSharp):
    1. using System;
    2.  
    3. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    4. public class InspectorDropdownAttribute : Attribute
    5. {
    6.  
    7.     public string[] Options;
    8.  
    9.     public InspectorDropdownAttribute(params string[] options)
    10.     {
    11.         Options = options;
    12.     }
    13.  
    14.     public InspectorDropdownAttribute(params object[] options)
    15.     {
    16.         Options = Array.ConvertAll(options, item => (string)Convert.ChangeType(item, typeof(string)));
    17.     }
    18. }
    19.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using UnityEditor;
    4. using FullInspector;
    5.  
    6. [CustomAttributePropertyEditor(typeof(InspectorDropdownAttribute), ReplaceOthers = true)]
    7. public class InspectorDropdownAttributeEditor<TElement> : AttributePropertyEditor<TElement, InspectorDropdownAttribute>
    8. {
    9.     private static T Cast<T>(object o)
    10.     {
    11.         return (T)Convert.ChangeType(o, typeof(T));
    12.     }
    13.  
    14.     protected override TElement Edit(Rect region, GUIContent label, TElement element, InspectorDropdownAttribute attribute, fiGraphMetadata metadata)
    15.     {
    16.         int index = Array.FindIndex(attribute.Options, e => e.Equals(Cast<string>(element)));
    17.         if (element == null || element.Equals(default(TElement)) || index == -1)
    18.         {
    19.             element = Cast<TElement>(attribute.Options[0]);
    20.             index = 0;
    21.         }
    22.  
    23.         return Cast<TElement>(attribute.Options[EditorGUI.Popup(region, label.text, index, attribute.Options, EditorStyles.popup)]);
    24.     }
    25.  
    26.     protected override float GetElementHeight(GUIContent label, TElement element, InspectorDropdownAttribute attribute, fiGraphMetadata metadata)
    27.     {
    28.         return EditorStyles.popup.CalcHeight(label, 100);
    29.     }
    30. }
    31.  
     
    SugoiDev likes this.
  17. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    @Ardenian

    Awesome!
    The major limitation for attributes is that they can't be generic, so it's harder to implement a generic constrained dropdown that works with any type. But I usually prefer to use attributes when I can, because they don't force me to use containers just to get a nice inspector.


    Do post here or in github if you have something cool. I also eye-balled naughty attributes before. A few nice things there, and it's MIT!
     
  18. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    @SugoiDev
    Yes, I agree, that's a huge disadvantage, as it involves a lot of typecasting when using them.
    InspectorDropdownAttribute
    should work with primitive data types, with types that can be converted to string representation and back without loss of information. Using attributes, my primary motivation was to get rid of the container. They are fine, but just for Inspector stuff, well, I agree here with you as well.

    Naughty Attributes does not work with FullInspector though, does it? I tried some, but they did not seem to affect the inspector, making me assume that FullInspector fully replaces the default inspector or at least to an extent which usually disables common Unity
    PropertyDrawers
    .

    I fiddled a bit around with
    InspectorProgressbarAttribute
    and tinkered something together, but I am not satisfied with it. While it does work, it does not with some other attributes, as example
    InspectorDisabledAttribute
    . Could still be useful, but shouldn't be used with other attributes, if possible.

    I think I focus on programming on my actual game for now, every minute spent on Editor is a minute not spent on one's game, they say, don't they. It is a great shame this tool won't be continued.

    Code (CSharp):
    1. using FullInspector;
    2. using UnityEngine;
    3.  
    4. public class Test : BaseBehavior
    5. {
    6.     [SerializeField]
    7.     [InspectorProgressBar(100, "red", true, false)]
    8.     private float myProgress;
    9.  
    10.     void Update()
    11.     {
    12.         myProgress += 1 * Time.deltaTime;
    13.     }
    14. }
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    5. public class InspectorProgressBarAttribute : Attribute
    6. {
    7.  
    8.     public double Max;
    9.  
    10.     public Color Color;
    11.  
    12.     public bool RoundToInt;
    13.  
    14.     public bool ShowPropertyField;
    15.  
    16.     public InspectorProgressBarAttribute(double max, string color, bool roundToInt, bool showPropertyField = true)
    17.     {
    18.         Color = GetColor(color);
    19.         Max = max;
    20.         RoundToInt = roundToInt;
    21.         ShowPropertyField = showPropertyField;
    22.     }
    23.     public InspectorProgressBarAttribute(double max, float r, float g, float b, bool roundToInt)
    24.     {
    25.         Color = new Color(r, g, b);
    26.         Max = max;
    27.         RoundToInt = roundToInt;
    28.     }
    29.     public InspectorProgressBarAttribute(double max, float r, float g, float b, float a, bool roundToInt)
    30.     {
    31.         Color = new Color(r, g, b, a);
    32.         Max = max;
    33.         RoundToInt = roundToInt;
    34.     }
    35.  
    36.     private Color GetColor(string identifier)
    37.     {
    38.         switch (identifier)
    39.         {
    40.             case "black":
    41.                 return Color.black;
    42.             case "blue":
    43.                 return Color.blue;
    44.             case "clear":
    45.                 return Color.clear;
    46.             case "cyan":
    47.                 return Color.cyan;
    48.             case "gray":
    49.                 return Color.gray;
    50.             case "green":
    51.                 return Color.green;
    52.             case "grey":
    53.                 return Color.grey;
    54.             case "magenta":
    55.                 return Color.magenta;
    56.             case "red":
    57.                 return Color.red;
    58.             case "yellow":
    59.                 return Color.yellow;
    60.             default:
    61.                 return Color.white;
    62.         }
    63.     }
    64. }
    65.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using UnityEditor;
    4. using FullInspector;
    5.  
    6. [CustomAttributePropertyEditor(typeof(InspectorProgressBarAttribute), ReplaceOthers = true)]
    7. public class InspectorProgressBarAttributeEditor<TElement> : AttributePropertyEditor<TElement, InspectorProgressBarAttribute>
    8. {
    9.     private static T Cast<T>(object o)
    10.     {
    11.         return (T)Convert.ChangeType(o, typeof(T));
    12.     }
    13.  
    14.     protected override TElement Edit(Rect region, GUIContent label, TElement element, InspectorProgressBarAttribute attribute, fiGraphMetadata metadata)
    15.     {
    16.         if (attribute.ShowPropertyField)
    17.         {
    18.             EditorGUI.LabelField(new Rect(region.x, region.y, region.width / 2, region.height / 2), label);
    19.             if (element is int) { element = Cast<TElement>(EditorGUI.IntField(new Rect(region.x + region.width / 2, region.y, region.width / 2, region.height / 2), Cast<int>(element))); }
    20.             else if (element is float) { element = Cast<TElement>(EditorGUI.FloatField(new Rect(region.x + region.width / 2, region.y, region.width / 2, region.height / 2), Cast<float>(element))); }
    21.             else if (element is double) { element = Cast<TElement>(EditorGUI.DoubleField(new Rect(region.x + region.width / 2, region.y, region.width / 2, region.height / 2), Cast<double>(element))); }
    22.         }
    23.         else
    24.         {
    25.             EditorGUI.LabelField(new Rect(region.x, region.y, region.width, region.height / 2), label);
    26.         }
    27.         var filledRegion = new Rect(region.x, region.y + 16f, region.width, region.height / 2);
    28.         EditorGUI.DrawRect(filledRegion, new Color(0.5f, 0.5f, 0.5f));
    29.         filledRegion.width *= Cast<float>(element) / (float)attribute.Max;
    30.         EditorGUI.DrawRect(filledRegion, attribute.Color);
    31.         EditorGUI.DropShadowLabel(new Rect(region.x, region.y, region.width, region.height - 2), (attribute.RoundToInt ? Cast<string>(Cast<int>(element)) : Cast<string>(element)) + "/" + Cast<string>(attribute.Max));
    32.  
    33.         return element;
    34.     }
    35.  
    36.     protected override float GetElementHeight(GUIContent label, TElement element, InspectorProgressBarAttribute attribute, fiGraphMetadata metadata)
    37.     {
    38.         return EditorStyles.label.CalcHeight(label, 100f) + 16f;
    39.     }
    40.  
    41.     public override bool CanEdit(Type type)
    42.     {
    43.         return type == typeof(int) || type == typeof(float) || type == typeof(double);
    44.     }
    45. }
    46.  
     
  19. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    WHAAAAT?! Has this been discontinued? I bought it a while ago but have yet to use it and it is already abandoned? Luckily I bought it on sale.
     
  20. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    Word.


    It's a bit crazy to me to that it is still on the store, actually.
    There has been no support for over a year (almost 2, actually), and the developer has been away from C# for quite a while.
     
    Ardenian likes this.
  21. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    I think if you use "ReplaceOthers = false" here it should work with other attributes.
     
    Ardenian likes this.
  22. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    I myself haven't encountered any severe bugs yet and it seems to work like a charm.
    If you know how to code, especially when it comes to Generics and Interfaces, this asset is a must-have, I would say.
    However, it is gambling, since you never know if you will encounter something which then cannot be treated since it is discontinued.

    Thank you, works like a charm!

    I posted the updated version along with the other attribute at Gist.
     
  23. 769270865

    769270865

    Joined:
    Feb 10, 2016
    Posts:
    23
    Hi, I just found out this asset and trying out the trial version, the trial version of 2.6.2 says it is made for Unity5.1, does it work with the current generation of Unity2017/2018?
    In Unity2017.2 looks like it doesn't recognize BaseBehavior as a replacement for MonoBehaviro
     
  24. SugoiDev

    SugoiDev

    Joined:
    Mar 27, 2013
    Posts:
    395
    If this is a new project, I don't recommend using FullInspector anymore.
    It has been abandoned by the original developer and the current license is too restrictive for community development.

    One alternative is Odin Inspector. It's fairly high quality, actively developed and supports newer Unity versions.

    If you really want to use FullInspector, you might have to do some work to get it working on newer Unitys. I'm currently using it on 2018.2, so I can at least say it can work.
     
    rakkarage likes this.
  25. MapMan

    MapMan

    Joined:
    Oct 27, 2013
    Posts:
    38
    Hi! I'm upgrading my project to Unity 2018.1 from 5.6 and I'm experiencing hard game crashes on Android when spawning BaseBehaviors at runtime. The same BaseBehavior can be preinstantiated on scene and it doesn't lead to any issues; try and spawn the same object at runtime and it crashes. I posted all the details in another thread:
    https://forum.unity.com/threads/uni...ng-an-object-with-a-certain-component.533642/

    Any ideas what's going on?
     
  26. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683