Loading UXML dynamically at runtime eg: LoadAssetAtPath

Hi all,
I’ve followed the documentation for loading xml from c# here:

But this doesn’t work in a build, because AssetDatabase is in the UnityEditor namespace…

How do I do this for run time?

Hi!

Are you trying to build a runtime or editor interface? You will get your asset differently between both cases.

A runtime build.

Thanks for the clarification!

Referencing asset by string is problematic in a build because only referenced assets are package in a build and the referencing system cannot predict all the string manipulation that you would be possible in the c# code. Because of this you need an explicit reference to your assets.

To display a UI on the runtime, you will need to use the UiDocument Monobehavior on an object of your scene and set the field inside to reference your ui.

If you want to reference multiple uxml programmatically, you would need to add serialized fields to your monobehaviors referencing the different files, for example.

That being said, the UXML and USS have scripted importers that detect and resolve all references so that they are included in the build. Because of this, a third approach could be to have a “master” uxml referenced by the UI document that contains everything and where you use query to hide/show some elements. This would work well for a menu with multiple page for example.

This is a high level answer, don’t hesitate to ask more specific questions if you have any!

1 Like

I see. So a good solution would be a mono sitting somewhere that has a reference to any and all UXML that might need to be dynamically added, then reference those fields instead.

That will be annoying to maintain, so I will likely write something that automates generating those references and just run it on a function attributed with [UnityEditor.Callbacks.DidReloadScripts]
Then I can make the keys of the files their filepaths - and everything should work fine

Related question: What about using the element? Is there a way I can put a template in my uxml, then grab that with a root.Q() and CloneTree on it?

Instead of using a “mono”, you could also use a scriptableObject if it is meant to be share with a lot of elements.

I will be back with the answer for the “root.Q() and CloneTree on it?”

In the meantime, can you describe what you are trying to achieve? I may help us guide you in an easier path…

root.Q will return a VisualElement, not a VisualTreeAsset. There is no way to clone a visual element or to get the VisualTreeAsset from a VisualElement.

We want to provide a way to get the VisualTreeAsset a visualElement was cloned from in 21.2, but will focus on stability first, so the feature may be delayed.

ok good to know.

What I’m doing is basically just developing a standardized way of importing things to a current rendered uxml doc.

Examples are:

  1. A map where the “pins” are separate uxml files we clone and drop on an element and set their positions, userData, and tooltips etc based on a data source determined at run time.

  2. A Generic popup dialog window that can be created at any time on any screen and filled with say a warning, a message, a small form etc…

I want the artists to be able to work on as many individual components of the ui by taking advantage of the fact that these things are put together at run time, and it means we won’t run in to merge conflicts as frequently: being that individual files will be accessed rather than one giant ui doc that contains everything

1 Like

You are having the right approach for now!

So for others who might find this thread and or relevant - here’s what I’ve come up with. Havne’t fully tested yet, but I think this is gonna work:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace UI
{
    /// <summary>
    /// Used to store references to all uxml files in the project for dynamic loading both in run time builds
    /// and editor
    /// </summary>
    public class UIDocumentCache : ScriptableObject
    {
        private static UIDocumentCache _instance;

        [SerializeField]
        [Tooltip("Loaded UXML file keys - do not manually adjust")]
        private string[] _keysUXML;

        [SerializeField]
        [Tooltip("Loaded UXML files - do not manually adjust")]
        private VisualTreeAsset[] _valuesUXML;


        /// <summary>
        /// Mapped assets against their keys
        /// </summary>
        private Dictionary<string, VisualTreeAsset> _uxmlAssetData;

        /// <summary>
        /// Get a preloaded visual tree asset for cloning to a UIDocument
        /// where the key is a dot syntax file path name relative to the UITK folder
        /// </summary>
        /// <param name="pKey">eg: screens.warzonescreen.warzonescreen</param>
        /// <returns></returns>
        public static VisualTreeAsset GetUXMLAsset(string pKey)
        {
            if(_instance == null)
            {
                //Initialization should happen from within the UI.Init method
                throw new System.Exception("[UIDocumentCache] The system has not been initialized");
            }
            if(string.IsNullOrEmpty(pKey) || string.IsNullOrWhiteSpace(pKey))
            {
                throw new System.Exception("[UIDocumentCache] The key cannot be null, whitespace, or empty");
            }
            string key = pKey.ToLower();
            if(!_instance._uxmlAssetData.ContainsKey(key))
            {
                throw new System.Exception("[UIDocumentCache] The key: " + key + " could not be found");
            }

            return _instance._uxmlAssetData[key];
        }


        /// <summary>
        /// Initializes the system - in editor, this will rebuild the mapping of all UXML files every time
        /// it's called.
        /// In a runtime build: it just loads the data
        /// </summary>
        public static void Initialize()
        {
            Object cache = Resources.Load("UIDocumentCache", typeof(UIDocumentCache));
            _instance = (UIDocumentCache)cache;

#if UNITY_EDITOR
            //If we are in the editor: then everytime we call init - let's scan and update the document cache
            _instance._keysUXML = null;

            //The root path of all ui files in our project
            string rootPath = Path.Combine(Application.dataPath, "Code/UI/UITK");

            List<string> _allUXML = IO.Helpers.FileSystem.GetFiles(rootPath, "*.uxml", SearchOption.AllDirectories);

           
            List<string> _uxmlKeys = new List<string>();
            List<VisualTreeAsset> _uxmlAssets = new List<VisualTreeAsset>();

            for (int i = 0; i < _allUXML.Count; i++)
            {
                //Unity wants a path relative to the project directory - which the Application class doesn't have... for raisons...
                string _relativePath = _allUXML[i].Replace(Application.dataPath,"Assets");
                _uxmlAssets.Add(AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(_relativePath));

                //Generate the keys: eg
                //  "components.someuxmlfilename"
                //  "somefilename"
                string key = _allUXML[i].Replace(rootPath, "").ToLower();
                key = key.Substring(1).Replace(".uxml","").Replace("\\",".");

                _uxmlKeys.Add(key);
            }
           
            _instance._keysUXML = _uxmlKeys.ToArray();
            _instance._valuesUXML = _uxmlAssets.ToArray();
#endif      //Done in editor

            //Now we can just put the lists to a dictionary for use in engine
            _instance._uxmlAssetData = new Dictionary<string, VisualTreeAsset>();
            for(int i = 0; i < _instance._keysUXML.Length; i++)
            {
                _instance._uxmlAssetData.Add(_instance._keysUXML[i], _instance._valuesUXML[i]);
            }
        }
    }
}
4 Likes

I think, use static instance - bad solution. In my project, we use Zenject DI, but I can’t inject to visual element class.
A good solution would be opportunity to add VisualTreeAsset link in inspector of VisualElement.

hmm well… It’ll never change during the course of the games lifespan, and there really can be only one. I think dependency injection is overkill for this when it’s point is to basically replace the already static method:
AssetDatabase.LoadAssetAtPath - no?

I’m talking about a big business project with dozens of assets in different folders. LoadAssetAtPath is not reliable and needs support. DI is used to eliminate Unity lifecycle problems. It himself determines who and when the injection is needed

I’m using the Resources system for runtime (which means a lot of careful naming!!). Of course, Unity has been discouraging the use of Resources in favor of the Addressables system, so eventually I’ll have to make that work. In my case, this is an RPG with things like a list of Quests (dynamically added), and a shop with rows, and Inventory with slots, etc… in these cases, it seems best to clone a VisualTreeAsset for each line rather than include them in the tree in the first place and fill them in or style.display=DisplayStyle.None them.

1 Like

I have tried your code and I get a null reference when calling UIDocumentCahe.Initialize(). I tried to put the file in Assets/Resources folder with no results :frowning:

This is the most annoying problem of Unity. Serialized fields have always been a problem.
Is there a way to force Unity to include all *.uxml files in build? Can I override something? Or can asset bundles be a good solution?

Hey,

A while back I did my best to summarize all of the available methods:

I think Adressables/AssetBundles are the best solution, Resources can be ok for prototyping but aren’t recommended for production.

1 Like

Concerning addressables, I am just going to shamelessly put some spotlight on this issue:

Would be nice if we could use Addressables.LoadAssetsAsync<VisualTreeAsset> instead of loading one asset at a time.

I could just be misunderstanding, but it seems like with any of these methods, it becomes difficult to build reusable UI. For instance, if I want to make a custom control, I can’t really make use of UXML or USS outside of C# code since there’s no way to grab asset references at runtime without using Resources/Addressables. Am I missing something?

You are not. These are the ways to load assets dynamically in Unity.

2 Likes