Manually drawing a list in a ValueDrawer
We have a specific use-case where the struct that needs to be inspected cannot have the data we want to inspect in the type definition.
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct EntityHandle : IEquatable<EntityHandle>
{
public int Id;
public int Version;
#if UNITY_EDITOR
// List drawer settings make sure we can't add or remove items from the list, nor rearrange them by dragging (I presume you have your own preferred way of doing that?)
[ShowInInspector] [ListDrawerSettings(DraggableItems = false, IsReadOnly = true, Expanded = false)]
[BlueprintComponentWrapper] public List<object> Components;
#endif
}
[OdinDrawer]
public class EntityHandleValueDrawer : OdinValueDrawer<EntityHandle> {
protected override void DrawPropertyLayout(IPropertyValueEntry<EntityHandle> entry, GUIContent label)
{
EntityHandle value = entry.SmartValue;
SirenixEditorGUI.BeginBox();
SirenixEditorGUI.BeginBoxHeader();
{
if (label != null) GUILayout.Label(label);
}
SirenixEditorGUI.EndBoxHeader();
var components = Engine.Instance.Context.Entities
.GetAllComponentsOfEntity(value.Id).ToList();
value.Components = components;
entry.SmartValue = value;
entry.ApplyChanges();
entry.Update();
this.CallNextDrawer(entry, null);
//SirenixEditorGUI.BeginVerticalList();
//SirenixEditorGUI.BeginListItem();
//GUILayout.Label(label);
//SirenixEditorGUI.EndListItem();
//SirenixEditorGUI.BeginListItem();
//GUILayout.Label(label);
//SirenixEditorGUI.EndListItem();
//SirenixEditorGUI.BeginListItem();
//GUILayout.Label(label);
//SirenixEditorGUI.EndListItem();
//SirenixEditorGUI.EndVerticalList();
SirenixEditorGUI.EndBox();
}
}
Just temporarily I let the components list exist in the EntityHandle struct, hoping to fill it up within the drawer before drawing. However that doesn't appear to work - The list stays null.
As a longer-term solution I wanted to call the drawer behind the [BlueprintComponentWrapper] attribute to draw the list. Is it possible to call this drawer and pass in a List<object> that is stored on the EntityHandleValueDrawer as a local private property?
Example:
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct EntityHandle : IEquatable<EntityHandle>
{
public int Id;
public int Version;
}
[OdinDrawer]
public class EntityHandleValueDrawer : OdinValueDrawer<EntityHandle> {
private List<object> _components;
protected override void DrawPropertyLayout(IPropertyValueEntry<EntityHandle> entry, GUIContent label)
{
EntityHandle value = entry.SmartValue;
_components = Engine.GetComponentsForEntity(value.Id);
this.CallAttribDrawer(BlueprintComponentWrapper, _components);
SirenixEditorGUI.EndBox();
}
}
Comments (6)
-
-
- changed status to open
If you want to have it serialize a list of system.object's you need to force Odin to serialize the struct instead of Unity.
There are a couple of ways you can do that:
1:
public class MyComponent : SerializedMonoBehaviour { public MyStruct MyStruct; } // Not marking it [Serializable] means that Unity will not be serializing it, however Odin will. public struct MyStruct { public List<System.Object> SomeList; }
2:
public class MyComponent : SerializedMonoBehaviour { [NonSerialized] // Force Unity not to serialize it. [OdinSerialize] // But tell Odin to serialize it instead. public MyStruct MyStruct; } [Serializable] // Now unity will serialize it. public struct MyStruct { public List<System.Object> SomeList; }
-
reporter Nope no serialization happening here. Its purely run-time data in the List<object>. The objects are in fact strongly typed value structs. Very similar to what's going on here in this ticket: https://bitbucket.org/sirenix/odin-inspector/issues/87/odindrawers-for-scripableobjects#comment-37225182
However in this case we can't store the data on the inspected object.
-
There is a fairly advanced/obscure method for drawing arbitrary data using Odin, despite it not being in the actual inspected property tree. We make use of it in a few different drawers ourselves. It involves creating a new property tree as a context value for the property in question, and then drawing that property tree in the drawer. It could be done like this:
[OdinDrawer] public class EntityHandleValueDrawer : OdinValueDrawer<EntityHandle> { protected override void DrawPropertyLayout(IPropertyValueEntry<EntityHandle> entry, GUIContent label) { var componentPropertyTreeContext = entry.Property.Context.Get(this, "component_tree", (PropertyTree<ComponentContainer>)null); if (componentPropertyTreeContext.Value == null) { // Initialize the property tree var container = new ComponentContainer(); componentPropertyTreeContext.Value = new PropertyTree<ComponentContainer>(new ComponentContainer[] { container }); } // Update components every frame // If you only need to update it once on initialization, just move this line into the above if statement (componentPropertyTreeContext.Value.WeakTargets[0] as ComponentContainer).Components = Engine.GetComponentsForEntity(entry.SmartValue.Id); // Draw the tree for the component container, which will draw the components list that was fetched from the entity handle componentPropertyTreeContext.Value.Draw(false); } private class ComponentContainer // Property trees can't draw lists directly, only normal types, so we need to store the list in a normal type { [ShowInInspector, BlueprintComponentWrapper] public List<object> Components; } }
Hope that helps.
Edit: I should note that that's just one approach, and is a little messy, though it should work. It's a little odd that your value just disappears - your example should actually work. I note that you're calling "entry.Update()" after setting it. Does it work better if you call "entry.Property.Update()" instead?
-
reporter Hrm interesting! Thanks that works!
-
- changed status to resolved
- Log in to comment
Hi Scott,
It looks like the struct is being serialized by Unity? In which case the list of components will not be serialized because it's a list of system.object's. That would explain the values disappearing. Try changing the type of the list to List<UnityEngine.Object / UnityEngine.Component> instead of List<System.Object / object>
As a rule of thumb; ShowInInspector is only meant to show values that are not necessarily being serialized.