Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Loading UXML dynamically at runtime eg: LoadAssetAtPath

Discussion in 'UI Toolkit' started by jnhackleman, Jan 29, 2021.

  1. jnhackleman

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
  2. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    Hi!

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

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
    A runtime build.
     
  4. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    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!
     
    yukunlinykl likes this.
  5. jnhackleman

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
    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 <Template> element? Is there a way I can put a template in my uxml, then grab that with a root.Q<Template>() and CloneTree on it?
     
  6. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    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<Template>() 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..
     
  7. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    root.Q<TemplateContainer> 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.
     
  8. jnhackleman

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
    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
     
    SimonDufour likes this.
  9. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    You are having the right approach for now!
     
  10. jnhackleman

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
    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:


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6. #if UNITY_EDITOR
    7. using UnityEditor;
    8. #endif
    9.  
    10. namespace UI
    11. {
    12.     /// <summary>
    13.     /// Used to store references to all uxml files in the project for dynamic loading both in run time builds
    14.     /// and editor
    15.     /// </summary>
    16.     public class UIDocumentCache : ScriptableObject
    17.     {
    18.         private static UIDocumentCache _instance;
    19.  
    20.         [SerializeField]
    21.         [Tooltip("Loaded UXML file keys - do not manually adjust")]
    22.         private string[] _keysUXML;
    23.  
    24.         [SerializeField]
    25.         [Tooltip("Loaded UXML files - do not manually adjust")]
    26.         private VisualTreeAsset[] _valuesUXML;
    27.  
    28.  
    29.         /// <summary>
    30.         /// Mapped assets against their keys
    31.         /// </summary>
    32.         private Dictionary<string, VisualTreeAsset> _uxmlAssetData;
    33.  
    34.         /// <summary>
    35.         /// Get a preloaded visual tree asset for cloning to a UIDocument
    36.         /// where the key is a dot syntax file path name relative to the UITK folder
    37.         /// </summary>
    38.         /// <param name="pKey">eg: screens.warzonescreen.warzonescreen</param>
    39.         /// <returns></returns>
    40.         public static VisualTreeAsset GetUXMLAsset(string pKey)
    41.         {
    42.             if(_instance == null)
    43.             {
    44.                 //Initialization should happen from within the UI.Init method
    45.                 throw new System.Exception("[UIDocumentCache] The system has not been initialized");
    46.             }
    47.             if(string.IsNullOrEmpty(pKey) || string.IsNullOrWhiteSpace(pKey))
    48.             {
    49.                 throw new System.Exception("[UIDocumentCache] The key cannot be null, whitespace, or empty");
    50.             }
    51.             string key = pKey.ToLower();
    52.             if(!_instance._uxmlAssetData.ContainsKey(key))
    53.             {
    54.                 throw new System.Exception("[UIDocumentCache] The key: " + key + " could not be found");
    55.             }
    56.  
    57.             return _instance._uxmlAssetData[key];
    58.         }
    59.  
    60.  
    61.         /// <summary>
    62.         /// Initializes the system - in editor, this will rebuild the mapping of all UXML files every time
    63.         /// it's called.
    64.         /// In a runtime build: it just loads the data
    65.         /// </summary>
    66.         public static void Initialize()
    67.         {
    68.             Object cache = Resources.Load("UIDocumentCache", typeof(UIDocumentCache));
    69.             _instance = (UIDocumentCache)cache;
    70.  
    71. #if UNITY_EDITOR
    72.             //If we are in the editor: then everytime we call init - let's scan and update the document cache
    73.             _instance._keysUXML = null;
    74.  
    75.             //The root path of all ui files in our project
    76.             string rootPath = Path.Combine(Application.dataPath, "Code/UI/UITK");
    77.  
    78.             List<string> _allUXML = IO.Helpers.FileSystem.GetFiles(rootPath, "*.uxml", SearchOption.AllDirectories);
    79.  
    80.            
    81.             List<string> _uxmlKeys = new List<string>();
    82.             List<VisualTreeAsset> _uxmlAssets = new List<VisualTreeAsset>();
    83.  
    84.             for (int i = 0; i < _allUXML.Count; i++)
    85.             {
    86.                 //Unity wants a path relative to the project directory - which the Application class doesn't have... for raisons...
    87.                 string _relativePath = _allUXML[i].Replace(Application.dataPath,"Assets");
    88.                 _uxmlAssets.Add(AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(_relativePath));
    89.  
    90.                 //Generate the keys: eg
    91.                 //  "components.someuxmlfilename"
    92.                 //  "somefilename"
    93.                 string key = _allUXML[i].Replace(rootPath, "").ToLower();
    94.                 key = key.Substring(1).Replace(".uxml","").Replace("\\",".");
    95.  
    96.                 _uxmlKeys.Add(key);
    97.             }
    98.            
    99.             _instance._keysUXML = _uxmlKeys.ToArray();
    100.             _instance._valuesUXML = _uxmlAssets.ToArray();
    101. #endif      //Done in editor
    102.  
    103.             //Now we can just put the lists to a dictionary for use in engine
    104.             _instance._uxmlAssetData = new Dictionary<string, VisualTreeAsset>();
    105.             for(int i = 0; i < _instance._keysUXML.Length; i++)
    106.             {
    107.                 _instance._uxmlAssetData.Add(_instance._keysUXML[i], _instance._valuesUXML[i]);
    108.             }
    109.         }
    110.     }
    111. }
    112.  
    113.  
     
    SBods9A and SimonDufour like this.
  11. RKar

    RKar

    Joined:
    Mar 6, 2019
    Posts:
    22
    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.
     
  12. jnhackleman

    jnhackleman

    Joined:
    Apr 24, 2017
    Posts:
    12
    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?
     
  13. RKar

    RKar

    Joined:
    Mar 6, 2019
    Posts:
    22
    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
     
  14. Tkrain42

    Tkrain42

    Joined:
    Jul 25, 2015
    Posts:
    40
    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.
     
    MurphyMurph_21 likes this.
  15. vrivon

    vrivon

    Joined:
    Oct 7, 2021
    Posts:
    17

    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 :(
     
    Last edited: Nov 13, 2021
  16. Denis-535

    Denis-535

    Joined:
    Jun 26, 2022
    Posts:
    21
    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?
     
    Last edited: Aug 20, 2022
  17. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    733
    KamilCSPS likes this.
  18. KamilCSPS

    KamilCSPS

    Joined:
    May 21, 2020
    Posts:
    377
    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.
     
  19. VoodooDetective

    VoodooDetective

    Joined:
    Oct 11, 2019
    Posts:
    238

    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?
     
  20. antoine-unity

    antoine-unity

    Unity Technologies

    Joined:
    Sep 10, 2015
    Posts:
    733
    You are not. These are the ways to load assets dynamically in Unity.
     
    TomTrottel and VoodooDetective like this.
  21. Parlay-Creative

    Parlay-Creative

    Joined:
    Sep 14, 2013
    Posts:
    7
    This is covered in the link provided by antoine-unity, see Load by Label.