- Home /
UIElements or IMGUI for dynamic custom inspectors
I want to build a custom inspector that renders a list of objects and contains a button. Pressing the button should add a new element to the list. The inspector should render the list including the new element afterwards.
Since the UI is changing (one more element is rendered each time the button is pressed) I am unsure if using UIElements is a good approach. As far as I understood in UIElements you define the UI elements in CreateInspectorGUI, which is called the first time that the inspector is drawn. But you don't define what happens each frame. This is more like the approach in IMGUI.
Would IMGUI be more suitable for this or can I still work with UIElements?
I hope that somebody more into these topics can nudge me in the right direction. :)
Thanks!
Answer by exploringunity · May 14, 2020 at 02:50 AM
Hey @MogwaiHawk,
Edit: I wrote an in-depth tutorial on how to create dynamic custom inspectors like the one below. You can check it out at Exploring UIElements Part 4: Monster Inspector.
I would definitely recommend UIElements for this. You are correct that you define your UI elements in CreateInspectorGUI. That is also where you can set up event handlers to make your UI dynamic. In your example, you could add a click handler to your button that instantiates some sort of VisualElement and uses the Add function to place the element on your UI. You can use the Remove or Clear functions to get rid of elements that are no longer needed.
Below is a simple, but complete, example I wrote to experiment with the idea. Hopefully it illustrates some of the concepts you are asking about. The idea is that you have a Potion class for your game that has a Title and a list of Effects, and you want a custom inspector for it using UIElements.
Here's the default inspector for our Potion class:
And a preview of the final result:
Assets/Potion.cs
using System.Collections.Generic;
using UnityEngine;
public class Potion : MonoBehaviour
{
public string title;
public List<string> effects;
}
Assets/Editor/PotionEditor.cs
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine.UIElements;
[CustomEditor(typeof(Potion))]
public class PotionEditor : Editor
{
Potion potion;
VisualElement effectEditorsContainer;
Label sizeLbl;
void OnEnable()
{
potion = (Potion)target;
if (potion.effects == null) { potion.effects = new List<string>(); }
}
public override VisualElement CreateInspectorGUI()
{
var mainTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/PotionEditor.uxml");
var ui = mainTemplate.Instantiate();
sizeLbl = ui.Q<Label>("EffectsListSize");
effectEditorsContainer = ui.Q("EffectsList");
var addEffectBtn = ui.Q<Button>("AddEffectBtn");
addEffectBtn.clicked += AddNewEffect;
RefreshUI();
return ui;
}
void AddEffect(int idx)
{
var effectUI = new PotionEffectEditor(potion, idx, DeleteEffect);
effectEditorsContainer.Add(effectUI);
}
void AddNewEffect()
{
potion.effects.Add(string.Empty);
EditorUtility.SetDirty(potion);
RefreshUI();
}
void DeleteEffect(PotionEffectEditor effectEditor)
{
potion.effects.RemoveAt(effectEditor.idx);
EditorUtility.SetDirty(potion);
RefreshUI();
}
void RefreshUI()
{
effectEditorsContainer.Clear();
foreach (var idx in Enumerable.Range(0, potion.effects.Count))
{
AddEffect(idx);
}
sizeLbl.text = $" Size: {potion.effects.Count}";
}
}
Assets/PotionEditor.uxml
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
<engine:VisualElement>
<engine:Style src="PotionEditor.uss" />
<engine:TextField name="PotionTitle" label="Title" binding-path="title" />
<engine:Label name="EffectsListHeader" text=" Effects" />
<engine:Label name="EffectsListSize" text="SIZE: ###" />
<engine:Button name="AddEffectBtn" text="Add New Effect" />
<engine:VisualElement name="EffectsList">
</engine:VisualElement>
</engine:VisualElement>
</engine:UXML>
Assets/PotionEditor.uss
.horizontalContainer { flex-direction: row; }
#AddEffectBtn { margin: 0 3px 0 20px; }
#DeleteBtn { color: red; }
#Effect { flex-grow: 1; }
Assets/PotionEffectEditor.cs
using UnityEditor;
using UnityEngine.UIElements;
using System;
public class PotionEffectEditor : VisualElement
{
public Potion potion;
public int idx;
Action<PotionEffectEditor> deleteCallback;
public PotionEffectEditor(Potion potion_, int idx_, Action<PotionEffectEditor> deleteCallback_)
{
idx = idx_;
potion = potion_;
deleteCallback = deleteCallback_;
var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/PotionEffectEditor.uxml");
var ui = template.Instantiate();
var effectField = ui.Q<TextField>("Effect");
effectField.label = $" Element {idx}";
effectField.value = potion.effects[idx];
effectField.RegisterValueChangedCallback(x => UpdatePotionEffect(x));
var deleteBtn = ui.Q<Button>("DeleteBtn");
deleteBtn.clicked += DeleteEffect;
Add(ui);
}
void DeleteEffect() { deleteCallback(this); }
void UpdatePotionEffect(ChangeEvent<string> change)
{
potion.effects[idx] = change.newValue;
EditorUtility.SetDirty(potion);
}
}
Assets/Editor/PotionEffectEditor.uxml
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
<engine:VisualElement class="horizontalContainer">
<engine:TextField name="Effect" />
<engine:Button name="DeleteBtn" text="X" />
</engine:VisualElement>
</engine:UXML>
Hope this helps!
Thank you very much for the detailed answer! :)
Your answer
Follow this Question
Related Questions
Detect if player wants to pause or to jump 3 Answers
Sprite Alpha Performance 0 Answers
Unable to Instantiate UI Object in Unity 0 Answers
Button.Select(); does not highlight? 12 Answers
A problem with the dropdown items 0 Answers