Search Unity

How To Render A Custom Struct In The Entity Inspector

Discussion in 'Graphics for ECS' started by kork, Apr 10, 2019.

  1. kork

    kork

    Joined:
    Jul 14, 2009
    Posts:
    280
    I have a special struct to represent hex grid coordinates. It basically looks like a
    float3 
    but uses int values and there are some constraints on the validity of combinations:

    Code (CSharp):
    1.  public struct TileCoordinates : IEquatable<TileCoordinates>
    2.     {
    3.         public int X { get; }
    4.         public int Y { get; }
    5.         public int Z { get; }
    6.   //...
    7. }
    I use this struct inside of a component:

    Code (CSharp):
    1.  
    2.     /// <summary>
    3.     /// Coordinates of a thing inside the hex world.
    4.     /// </summary>
    5.     public struct Coordinates: IComponentData
    6.     {
    7.         public TileCoordinates Value;
    8.     }
    This works all fine and dandy, however in the entity inspector, the contents of the Coordinates struct are not rendering:

    upload_2019-4-10_21-14-7.png

    Because
    TileCoordinates
    is a struct and not a
    UnityEngine.Object
    I cannot write a custom inspector for it the classic way. Is there any way i can render the contents of this struct properly in the inspector?
     
    OndrejP likes this.
  2. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Maybe using PropertyDrawer would work? It works for other custom structs in the regular inspector.
     
  3. kork

    kork

    Joined:
    Jul 14, 2009
    Posts:
    280
    Nope, I added a very primitive one just outputting a Debug.Log, but it doesn't even get called. There seems to be some special magic in place for the entity debugger.
     
  4. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Properties not work with ECS.

    With DOTS, you should put the validation of the data in a System and simplify the struct.

    Code (CSharp):
    1. public struct TileCoordinates : IEquatable<TileCoordinates>, IComponentData
    2. {
    3.         public int3 Value;
    4. }
     
  5. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    258
    make sure they're all [Serializable] (Edit: The structs, I mean)
     
  6. kork

    kork

    Joined:
    Jul 14, 2009
    Posts:
    280
    So [Serializable] didn't help, but changing the properties to public member variables made the struct render properly. I'm not really a huge fan of this, but looks like the Unity.Mathematics types are implemented the same way. They have some logic in the struct but ultimately you can throw all of this out of the window by directly writing the members. Thank you very much :)
     
  7. OndrejP

    OndrejP

    Joined:
    Jul 19, 2017
    Posts:
    304
    It requires a little bit of hacking (accessing private field of
    EntitySelectionProxyEditor
    ).

    How normal entity inspector works:
    • Entities cannot be selected because they're not UnityEngine.Object
    • there's EntitySelectionProxy which is ScriptableObject, it stores selected Entity
    • EntitySelectionProxyEditor draws inspector for EntitySelectionProxy
    • PropertyVisitor class from com.unity.properties is used to walk component field hierarchy
    • visitor adapters are used, they call EditorGUILayout.anything to draw inspector

    How this code works
    • it finds all types decorated with EntityInspectorAdapterAttribute
    • creates instances of those and stores them in list
    • when EntitySelectionProxy is selected, it adds adapters to it's editor

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Reflection;
    5. using System.Text;
    6. using System.Threading.Tasks;
    7. using Unity.Entities.Editor;
    8. using Unity.Properties;
    9. using UnityEditor;
    10. using UnityEngine;
    11.  
    12. [AttributeUsage(AttributeTargets.Class)]
    13. public class EntityInspectorAdapterAttribute : Attribute
    14. {
    15. }
    16.  
    17. public static class EntityInspectorExtension
    18. {
    19.     static Type m_proxyEditor = typeof(EntitySelectionProxy).Assembly.GetType("Unity.Entities.Editor.EntitySelectionProxyEditor");
    20.     static FieldInfo m_visitorField = m_proxyEditor.GetField("visitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    21.  
    22.     public static readonly List<IPropertyVisitorAdapter> Adapters = new List<IPropertyVisitorAdapter>();
    23.  
    24.     [InitializeOnLoadMethod]
    25.     static void Init()
    26.     {
    27.         Selection.selectionChanged += OnSelectionChanged;
    28.         foreach (var type in TypeCache.GetTypesWithAttribute<EntityInspectorAdapterAttribute>())
    29.         {
    30.             Adapters.Add((IPropertyVisitorAdapter)Activator.CreateInstance(type));
    31.         }
    32.     }
    33.  
    34.     private static void OnSelectionChanged()
    35.     {
    36.         if (Selection.activeObject.GetType() == typeof(EntitySelectionProxy))
    37.         {
    38.             var editor = Resources.FindObjectsOfTypeAll(m_proxyEditor)[0];
    39.             var visitor = (PropertyVisitor)m_visitorField.GetValue(editor);
    40.             foreach (var adapter in Adapters)
    41.             {
    42.                 visitor.AddAdapter(adapter);
    43.             }
    44.         }
    45.     }
    46. }
    47.  
    This is custom inspector for my component
    DebugName


    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. using Unity.Properties;
    7. using UnityEditor;
    8.  
    9. [EntityInspectorAdapter]
    10. public class EntityInspectorDebugName : IPropertyVisitorAdapter, IVisitAdapter<DebugName>
    11. {
    12.     public VisitStatus Visit<TProperty, TContainer>(IPropertyVisitor visitor, TProperty property, ref TContainer container, ref DebugName value, ref ChangeTracker changeTracker)
    13.         where TProperty : IProperty<TContainer, DebugName>
    14.     {
    15.         EditorGUI.BeginChangeCheck();
    16.  
    17.         value = new DebugName(EditorGUILayout.TextField(property.GetName(), value.Value.ToString()));
    18.  
    19.         if (EditorGUI.EndChangeCheck())
    20.         {
    21.             changeTracker.MarkChanged();
    22.         }
    23.         return VisitStatus.Handled;
    24.     }
    25. }
    26.  
     
    GilCat likes this.
  8. OndrejP

    OndrejP

    Joined:
    Jul 19, 2017
    Posts:
    304
    I've improved upon my original idea and put it onto GitHub:
    https://github.com/OndrejPetrzilka/EntityComponentInspector

    You can install it into Unity as package by adding this into manifest:
    "entity-inspector-extension": "https://github.com/OndrejPetrzilka/EntityComponentInspector.git"


    Usage examples in readme on GitHub
     
    GilCat and Chmyke like this.
  9. threedots1

    threedots1

    Joined:
    Oct 9, 2014
    Posts:
    88
    Thanks @OndrejP
    Working great for displaying my enum flags and unions
     
    OndrejP likes this.