Hello,
In Unity 2023.1.0a19, we have added the ability to add references to assets in the properties of your custom controls. This effectively means that you can drag references to Textures, Sprites or any kind of asset into the UI Builder Attributes inspector.
Intended usage
To use this feature, create custom controls in C# and declare properties of type UxmlAssetAttributeDescription.
The UI Builder will pick this up to display an object field and save the appropriate path in the UXML file.
In your UXML, the path to the asset is stored in the exact same way as references to UXML or USS files. The path is resolved at import time and captures a direct dependency to the imported asset referenced by this path.
This means that you do not need to rely on Resources folders to locate assets in your custom controls and extensions.
Until UI Toolkit gains the ability to save and author custom data structures, you can use this feature to leverage Unity’s existing capabilities for custom extensions around your UI Toolkit workflow. For example, you can create custom Scriptable Objects and use Unity’s serialization feature to save custom structures, and add custom Inspectors and Drawers to edit the data.
Example
As a quick proof of concept, we can revisit the idea of a custom element to display gradients from the Unity Manual.
First of all, define a custom Scriptable Object type, GradientDefinition
that holds a UnityEngine.Gradient property:
using UnityEngine;
namespace UIToolkitExamples
{
[CreateAssetMenu(fileName = "GradientDefinition", menuName = "GradientDefinition", order = 0)]
public class GradientDefinition : ScriptableObject
{
public Gradient gradient;
public void Reset()
{
gradient = new Gradient();
}
}
}
Then, define a custom controls that accepts a GradientDefinition
reference, alongside the necessary setup for receiving a reference from UXML/UI Builder.
using UnityEngine;
using UnityEngine.UIElements;
namespace UIToolkitExamples
{
public class ExampleElementCustomAsset : VisualElement
{
// Factory class, required to expose this custom control to UXML
public new class UxmlFactory : UxmlFactory<ExampleElementCustomAsset, UxmlTraits> { }
// Traits class
public new class UxmlTraits : VisualElement.UxmlTraits
{
public UxmlAssetAttributeDescription<GradientDefinition> m_Gradient = new() { name = "gradient" };
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
if (m_Gradient.TryGetValueFromBag(bag, cc, out GradientDefinition value))
{
((ExampleElementCustomAsset)ve).gradient = value;
}
}
}
private GradientDefinition m_Gradient;
public GradientDefinition gradient
{
get => m_Gradient;
set
{
if (m_Gradient == value)
return;
m_Gradient = value;
GenerateGradient();
}
}
// Image child element and its texture
Texture2D m_Texture2D;
Image m_Image;
public ExampleElementCustomAsset()
{
// Create an Image and a texture for it. Attach Image to self.
m_Texture2D = new Texture2D(100, 100);
m_Image = new Image();
m_Image.image = m_Texture2D;
Add(m_Image);
}
void GenerateGradient()
{
if (m_Gradient == null)
return;
for (int i = 0; i < m_Texture2D.width; ++i)
{
Color color = m_Gradient.gradient.Evaluate(i / (float)m_Texture2D.width);
for (int j = 0; j < m_Texture2D.height; ++j)
{
m_Texture2D.SetPixel(i, j, color);
}
}
m_Texture2D.Apply();
m_Image.MarkDirtyRepaint();
}
}
}
After creating an instance of GradientDefinition in the project, can edit its gradient
property in the regular Inspector (Unity provides a default Editor for the Gradient property).
After we put an instance of ExampleElementCustomAsset
in the UI Builder, we can edit its gradient property through an object field in its Inspector to reference the instance of GradientDefinition
.
Which gets saved in the UXML as follows:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<UIToolkitExamples.ExampleElementCustomAsset gradient="project://database/Assets/MyGradient.asset?fileID=11400000&guid=2b8b0b4d063cf4e5b974fef361238fb6&type=2#MyGradient" />
</ui:UXML>
Behind the scenes
We’ve added this feature because it’s a necessary building block for the data binding workflow that is in development.
The main challenge of the UxmlAssetAttributeDescription implementation is to detect the attributes of custom elements in order to correctly interpret asset paths and provide useful warning and errors when paths are broken.
This means that changes in C# may cause re-imports of UXML file to keep everything consistent (if attributes are added or removed, we need to re-evaluate if attributes are to be interpreted as paths).
You might be might interested to learn that this is partly made possible by the Custom Dependency feature of the Asset Database.
Conclusion
We hope that you will find this feature useful and encourage you to try it out. Feel free to post any question or feedback in this thread. Thanks!