How To Render A Custom Struct In The Entity Inspector

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:

 public struct TileCoordinates : IEquatable<TileCoordinates>
    {
        public int X { get; }
        public int Y { get; }
        public int Z { get; }
  //... 
}

I use this struct inside of a component:

    /// <summary>
    /// Coordinates of a thing inside the hex world.
    /// </summary>
    public struct Coordinates: IComponentData
    {
        public TileCoordinates Value;
    }

This works all fine and dandy, however in the entity inspector, the contents of the Coordinates struct are not rendering:

BecauseTileCoordinates 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?

1 Like

Maybe using PropertyDrawer would work? It works for other custom structs in the regular inspector.

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.

Properties not work with ECS.

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

public struct TileCoordinates : IEquatable<TileCoordinates>, IComponentData
{
        public int3 Value;
}

make sure they’re all [Serializable] (Edit: The structs, I mean)

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 :slight_smile:

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Unity.Entities.Editor;
using Unity.Properties;
using UnityEditor;
using UnityEngine;

[AttributeUsage(AttributeTargets.Class)]
public class EntityInspectorAdapterAttribute : Attribute
{
}

public static class EntityInspectorExtension
{
    static Type m_proxyEditor = typeof(EntitySelectionProxy).Assembly.GetType("Unity.Entities.Editor.EntitySelectionProxyEditor");
    static FieldInfo m_visitorField = m_proxyEditor.GetField("visitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

    public static readonly List<IPropertyVisitorAdapter> Adapters = new List<IPropertyVisitorAdapter>();

    [InitializeOnLoadMethod]
    static void Init()
    {
        Selection.selectionChanged += OnSelectionChanged;
        foreach (var type in TypeCache.GetTypesWithAttribute<EntityInspectorAdapterAttribute>())
        {
            Adapters.Add((IPropertyVisitorAdapter)Activator.CreateInstance(type));
        }
    }

    private static void OnSelectionChanged()
    {
        if (Selection.activeObject.GetType() == typeof(EntitySelectionProxy))
        {
            var editor = Resources.FindObjectsOfTypeAll(m_proxyEditor)[0];
            var visitor = (PropertyVisitor)m_visitorField.GetValue(editor);
            foreach (var adapter in Adapters)
            {
                visitor.AddAdapter(adapter);
            }
        }
    }
}

This is custom inspector for my component DebugName

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Unity.Properties;
using UnityEditor;

[EntityInspectorAdapter]
public class EntityInspectorDebugName : IPropertyVisitorAdapter, IVisitAdapter<DebugName>
{
    public VisitStatus Visit<TProperty, TContainer>(IPropertyVisitor visitor, TProperty property, ref TContainer container, ref DebugName value, ref ChangeTracker changeTracker)
        where TProperty : IProperty<TContainer, DebugName>
    {
        EditorGUI.BeginChangeCheck();

        value = new DebugName(EditorGUILayout.TextField(property.GetName(), value.Value.ToString()));

        if (EditorGUI.EndChangeCheck())
        {
            changeTracker.MarkChanged();
        }
        return VisitStatus.Handled;
    }
}
1 Like

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

2 Likes

Thanks @OndrejP
Working great for displaying my enum flags and unions

1 Like