I’m using Unity 2023.2.14f and was planning on making a dropdown that allows for multiple choice. Reading about EnumFlagsField and MaskField, I was struggling on finding it in the library of UI Builder. Finally, I saw that if I enabled Editor Extension Authoring I was able to find the types of elements I was looking for.
It worried me that all these useful elements seem to be editor-only, and I tried them in runtime and at least interactions seemed to work fine. What does it mean that these elements are editor-only? How would that affect my game by using it? It seems like my only option if I need a dropdown that allows for selecting multiple options at a time.
You probably tried it in playmode, which is still technically the editor. If you make a build, it will likely fail because the EnumFlagsField element is not available (resides in UnityEditor namespace).
Note that runtime UI still offers Dropdown and Enum elements.
Hi again!
Ah yes, you are right. I never made a build with it.
With the dropdown and enum elements, there is no possibility for multiple choices right? This is the first time using the UI Toolkit for an ambitious application, but I haven’t yet found a way to do this and that’s why I turned to the EnumFlagsField.
Just wanted to leave this here in-case anyone stumbles here, it is possible to create custom elements for this, below is a basic implementation that fit my needs.
public class EnumFlagsDropdownField<T> : BaseField<int> where T : struct, Enum
{
private readonly Button _button;
private readonly Label _buttonLabel;
private readonly Label _arrowLabel;
private int _mask;
public EnumFlagsDropdownField() : base(null, null)
{
Clear(); // clear out base elements that might be here
_button = new Button(OpenDropdown) { name = "flags-button" };
_button.style.flexGrow = 1;
_button.style.flexShrink = 1;
_button.style.alignSelf = Align.Stretch;
_button.text = "";
var container = new VisualElement(); // to hold arrow and text
container.style.flexDirection = FlexDirection.Row;
container.style.justifyContent = Justify.SpaceBetween;
container.style.alignItems = Align.Center;
container.style.flexGrow = 1;
container.style.flexShrink = 1;
_buttonLabel = new Label(); // main text label
_buttonLabel.style.flexGrow = 1;
_buttonLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
_arrowLabel = new Label("▼"); // arrow
_arrowLabel.style.unityTextAlign = TextAnchor.MiddleRight;
_arrowLabel.style.marginLeft = 5;
container.Add(_buttonLabel); // add items to visual element
container.Add(_arrowLabel);
_button.Add(container);
Add(_button); // add main button to main visual element
UpdateLabel(); // initial state
}
private void OpenDropdown()
{
var menu = new GenericDropdownMenu();
int allBits = 0;
// total bits calculation
foreach (T enumValue in Enum.GetValues(typeof(T)))
{
int bit = Convert.ToInt32(enumValue);
if (bit == 0) continue;
allBits |= bit;
}
// add None option
menu.AddItem("None", _mask == 0, () =>
{
SetValueWithoutNotify(0);
using var changeEvent = ChangeEvent<int>.GetPooled(_mask, 0);
changeEvent.target = this;
SendEvent(changeEvent);
});
// add Everuthing option
menu.AddItem("Everything", _mask == allBits, () =>
{
SetValueWithoutNotify(allBits);
using var changeEvent = ChangeEvent<int>.GetPooled(_mask, allBits);
changeEvent.target = this;
SendEvent(changeEvent);
});
menu.AddSeparator(""); // seperate add options with enum options
// add all enum options
foreach (T enumValue in Enum.GetValues(typeof(T)))
{
int bit = Convert.ToInt32(enumValue);
if (bit == 0)
continue;
bool isSet = (_mask & bit) != 0;
T valueCopy = enumValue;
menu.AddItem(enumValue.ToString(), isSet, () =>
{
int old = _mask;
if (isSet)
_mask &= ~bit;
else
_mask |= bit;
SetValueWithoutNotify(_mask);
using var changeEvent = ChangeEvent<int>.GetPooled(old, _mask);
changeEvent.target = this;
SendEvent(changeEvent);
});
}
menu.DropDown(_button.worldBound, _button);
}
// Update the label to show correct information (items that have been selected)
private void UpdateLabel()
{
string labelText;
if (_mask == 0)
labelText = "None";
else
{
var allValues = Enum.GetValues(typeof(T)).Cast<T>().Where(e => Convert.ToInt32(e) != 0).ToList();
int allBits = allValues.Sum(e => Convert.ToInt32(e));
if (_mask == allBits)
labelText = "Everything";
else
{
var selected = allValues.Where(e => (_mask & Convert.ToInt32(e)) != 0).ToList();
if (selected.Count > 1)
labelText = "Mixed";
else
labelText = selected.FirstOrDefault().ToString();
}
}
_buttonLabel.text = labelText;
}
public override void SetValueWithoutNotify(int newValue)
{
_mask = newValue;
base.SetValueWithoutNotify(newValue);
UpdateLabel();
}
}
then pair with this to create specific elements for use within the UI Builder
public class DropDownField : EnumFlagsDropdownField<YOUR_ENUM>
{
public new class UxmlFactory : UxmlFactory<DropDownField , UxmlTraits> { }
public new class UxmlTraits : BaseFieldTraits<int, UxmlIntAttributeDescription> { }
}
the main limitation of this solution is that you still need to explicitly write code for each Enum type but it is a working starting point if nothing else.
RBN