Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Official New feature : Drag and drop assets on your custom controls

Discussion in 'UI Toolkit' started by antoine-unity, Nov 18, 2022.

  1. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    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.

    Screen Shot 2022-11-18 at 4.05.05 PM.png

    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:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. namespace UIToolkitExamples
    4. {
    5.     [CreateAssetMenu(fileName = "GradientDefinition", menuName = "GradientDefinition", order = 0)]
    6.     public class GradientDefinition : ScriptableObject
    7.     {
    8.         public Gradient gradient;
    9.  
    10.         public void Reset()
    11.         {
    12.             gradient = new Gradient();
    13.         }
    14.     }
    15. }
    16.  
    Then, define a custom controls that accepts a
    GradientDefinition
    reference, alongside the necessary setup for receiving a reference from UXML/UI Builder.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UIElements;
    3.  
    4. namespace UIToolkitExamples
    5. {
    6.     public class ExampleElementCustomAsset : VisualElement
    7.     {
    8.         // Factory class, required to expose this custom control to UXML
    9.         public new class UxmlFactory : UxmlFactory<ExampleElementCustomAsset, UxmlTraits> { }
    10.  
    11.         // Traits class
    12.         public new class UxmlTraits : VisualElement.UxmlTraits
    13.         {
    14.             public UxmlAssetAttributeDescription<GradientDefinition> m_Gradient = new() { name = "gradient" };
    15.  
    16.             public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
    17.             {
    18.                 base.Init(ve, bag, cc);
    19.  
    20.                 if (m_Gradient.TryGetValueFromBag(bag, cc, out GradientDefinition value))
    21.                 {
    22.                     ((ExampleElementCustomAsset)ve).gradient = value;
    23.                 }
    24.             }
    25.         }
    26.  
    27.         private GradientDefinition m_Gradient;
    28.  
    29.         public GradientDefinition gradient
    30.         {
    31.             get => m_Gradient;
    32.             set
    33.             {
    34.                 if (m_Gradient == value)
    35.                     return;
    36.                 m_Gradient = value;
    37.                 GenerateGradient();
    38.             }
    39.         }
    40.  
    41.         // Image child element and its texture
    42.         Texture2D m_Texture2D;
    43.         Image m_Image;
    44.  
    45.         public ExampleElementCustomAsset()
    46.         {
    47.             // Create an Image and a texture for it. Attach Image to self.
    48.             m_Texture2D = new Texture2D(100, 100);
    49.             m_Image = new Image();
    50.             m_Image.image = m_Texture2D;
    51.             Add(m_Image);
    52.         }
    53.  
    54.         void GenerateGradient()
    55.         {
    56.             if (m_Gradient == null)
    57.                 return;
    58.  
    59.             for (int i = 0; i < m_Texture2D.width; ++i)
    60.             {
    61.                 Color color = m_Gradient.gradient.Evaluate(i / (float)m_Texture2D.width);
    62.                 for (int j = 0; j < m_Texture2D.height; ++j)
    63.                 {
    64.                     m_Texture2D.SetPixel(i, j, color);
    65.                 }
    66.             }
    67.  
    68.             m_Texture2D.Apply();
    69.             m_Image.MarkDirtyRepaint();
    70.         }
    71.     }
    72. }
    73.  
    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).
    Screen Shot 2022-11-18 at 3.53.27 PM.png

    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
    .
    Screen Shot 2022-11-18 at 4.05.05 PM.png

    Which gets saved in the UXML as follows:

    Code (CSharp):
    1. <ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    2.     <UIToolkitExamples.ExampleElementCustomAsset gradient="project://database/Assets/MyGradient.asset?fileID=11400000&amp;guid=2b8b0b4d063cf4e5b974fef361238fb6&amp;type=2#MyGradient" />
    3. </ui:UXML>
    4.  

    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!
     
    sandolkakos, _geo__, dlorre and 9 others like this.
  2. Kleptine

    Kleptine

    Joined:
    Dec 23, 2013
    Posts:
    274
    Neat!

    How does this serialization handle the case where you move the file-path of the asset that is depended on? Does that mean that moving a file may cause UXML assets to change? What happens if you move the asset, but don't update the UXML file with the new path?

    I'm mostly thinking about this because there aren't any other Unity systems that depend on asset path for referencing. Why not just serialize the guid+fileid?
     
  3. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    This is handled the same ways other assets are referenced from UXML and USS. UI Builder stores both the path and the guid+fileID in the same URI. This means that when files are moved, the reference will continue to resolve but a warning will be given in the console that the URI needs to be updated (The UI Builder will override to the correct path the next time it saves the file).

    Here is an example of the error given after moving the file:
    Code (csharp):
    1. Assets/ExampleCustomAssetUsage.uxml (3,6): Semantic - The specified URI does not exist in the current project : Asset reference to GUID '2b8b0b4d063cf4e5b974fef361238fb6' was moved from 'Assets/MyGradient.asset' to 'Assets/Common/MyGradient.asset'. Update the URL 'project://database/Assets/MyGradient.asset?fileID=11400000&guid=2b8b0b4d063cf4e5b974fef361238fb6&type=2#MyGradient' to remove this warning.
    This is not currently documented, nor do we have great support for making file moves seamless, but any future improvements over the current state will apply to usage of this attribute as well.
     
    Last edited: Nov 21, 2022
  4. Auroch-ChrisW

    Auroch-ChrisW

    Joined:
    May 4, 2022
    Posts:
    7
    Is this planned for release in 2022.x at all, or it it exclusively for 2023.x?
     
    SparkesRS likes this.
  5. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    No plans to backport currently
     
  6. Timboc

    Timboc

    Joined:
    Jun 22, 2015
    Posts:
    238
    Understand this is a feature but if it is at all considered, please take this post as a +1 for 2022 LTS - would be lovely not to use our current by-string hack.
     
    SparkesRS likes this.
  7. SkywardAssembly

    SkywardAssembly

    Joined:
    Dec 11, 2022
    Posts:
    4
    I'm assuming stringDefaultValue is read only on purpose, is there a way to set a default asset or address via code?
     
  8. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    1,298
    Another +1 for backporting

    If you have no plans on doing the backport yourself then may I ask for some guidance on how we could achieve this on our own.

    In general the UI Builder is much less extendable than the normal inspector. It would help so much if we could add custom UI and custom (persistent!) xml attributes. I am hoping for some sort of XMLAttribute class with an "OnInspectorGUI" like method that allows us to draw into a VisualElement container right below the normal "UxmlAttributeDescription" attributes. Together with an overridable serializeToString / deserializeFromString method this would give a lot of flexibility.

    I am currently making some assets for the UI Toolkit as I think it has a bright future. Compared to the normal inspector extending the UI Builder is VERY cumbersome (or downright impossible). I already need a lot of reflection code to get even the simplest things done, like query what UI Element is currently selected.

    Please let us add to your standard UI. It's what made the old inspector great and it could make the UI Builder great too. And please backport these at least to 2022. I know the official stance is that UITK is not yet ready for runtime [1], but people are using it already.

    Thank you for considering
     
    Last edited: Jun 7, 2023
    SparkesRS likes this.
  9. BSimonSweet

    BSimonSweet

    Joined:
    Aug 17, 2022
    Posts:
    67
    Backporting to 22.3 LTS would be so great ! It would be nice to consider it, as it is such a basic feature to reference asset in Unity. And there is no way to update to a non-LTS version right now ...

    Edit :
    UxmlAssetAttributeDescription
    is available in 22.3 but it is internal ...
     
    SparkesRS likes this.
  10. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    Will this work for runtime? I am creating custom controls wherein I need to change the image of each when I use them in the game UI. Can I do that using this code? If not, how?

    Thanks!
     
  11. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    I am not entirely sure I understand your question.

    UXML assets that receive references to other Unity assets will load correctly during runtime because the paths are resolved when the UXML is imported. As long as the custom element C# class is available in the runtime build, everything will work OK.

    If you are asking how to achieve something similar without UXML, then this is nothing different than using a regular property on a C# class, just like it would be with MonoBehaviours.

    If you have a list of images (Texture2D) to assign to a list of visual elements, just write C# to iterate through the lists and add the image to the style.backgroundImage property, or add a custom Texture2D property in a custom C# visual element class.
     
  12. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    My apologies for not being clearer. I want to create custom controls and instantiate them dynamically at runtime. For example, buttons in a list box wherein the name depends on player level. Can I change the display name of each button in a list box at runtime - that is during gameplay?
     
  13. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Given that UXMLTraits is deprecated and most of this stuff has moved to an attribute style system, how does this work now? I've tried everything I can think of with no success. I want to include a property to allow the user to set an image similar to how for example the Button control let's you set an Icon image.
     
  14. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,606
    It's very simple. This page explains it: https://docs.unity3d.com/2023.3/Documentation/Manual/UIE-custom-tag-name-and-attributes.html
     
  15. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Yeah this is the new system and works perfect for me for simple things like a string or a float but not for textures of other object type things like the gradient example.

    EDIT: Ok apparently this does work on Texture. I must have just been super tired last night working past midnight and done something stupid :(
     
    Last edited: Jan 15, 2024
    spiney199 likes this.
  16. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    I am interested in this as well. Maybe writing a texture attribute entirely from scratch is possible?
    By using an asmdef I can put my custom control into a namespace which has internal access to UxmlAssetAttributeDescription and it does show up ... upload_2024-3-13_11-48-57.png
     
    Last edited: Mar 13, 2024
  17. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    The reason it wasn't made public in this version is that, in the event that your code definition for the custom element changes (for example, add or remove an UxmlAssetAttributeDescription to the traits), affected UXML files do not get re-imported. So you may have an out of date import result until the next import of the UXML.

    It might not be an issue for some users but it wouldn't be solid enough for general availability.
     
    fleity likes this.
  18. fleity

    fleity

    Joined:
    Oct 13, 2015
    Posts:
    337
    @antoine-unity

    Would it work to write a UxmlTexture2dAttributeDescription : TypedUxmlAttributeDescription<Texture2D>?
    I tried and it showed up as a string instead not an asset reference onto which I could drop an asset or is the UxmlAssetAttributeDescription the only way to get this (because the whole hack with the asmdef isn't that flexible usually)?
     
  19. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    771
    No it won't work, TypedUxmlAttributeDescription is only meant to convert from something that can be stored as a raw string in UXML, and UI Builder only handles a specific set of subclasses.
     
    fleity likes this.