Search Unity

[SOLVED] Loading a VisualTreeAsset or StyleSheet in C# from anywhere

Discussion in 'UI Toolkit' started by TheHeftyCoder, Dec 27, 2019.

  1. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    Hello, I'd like to ask if there are any plans for a better loading of a VisualTree or Style. As it is right now, I can't move my .uss and .uxml files around because every path is absolute (well, it's relative to the Assets folder, but it's still absolute in the context that it's used). Is there a better-de facto way of loading these two assets?

    I've implemented a simple System.IO method that searches for a file in the Assets directory or its sub-directories (using EnumerateFiles from DirectoryInfo). Works like a charm, but there's a small delay in finding the files so Unity holds for 0.5 seconds. To eliminate the issue, I've made the VisualTreeAsset and StyleSheet load statically (as static fields) and only load if the fields have a null reference. It works even when changing the uxml or uss (since Unity probably reloads all the classes). Since I haven't seen any tutorial or any docs doing the same thing, I'd like to ask if this is correct or if it would cause any problems down the road.

    I'd still like an answer on if there's a better way that I have stupidly neglected or any plans ahead.

    Thanks!
     
  2. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    Why not make it serializable and link it in? Or perhaps load it from a path relative to the current file.
     
  3. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    I am implementing a ReorderableList as a VisualElement and I want it to read the default .uxml and default .uss I've provided. Since it's not a MonoBehaviour or ScriptableObject, it doesn't show the default field that other scripts would normally show, so changing it to serializable won't do - if that's what you actually meant.

    About a path relative to the current file, I'd love how to get that easily in Unity! I can't get LoadAssetAtPath to work with any relative pathing (I always need to input Assets/the_whole_path) and any other .NET way requires searching the whole directory (unless I'm missing something).

    I can see it working by forcing a custom ScriptableObject to work as a settings provider to the VisualElement, It would need to have a static method that returns the objects assigned to the fields and then you can probably do all sorts of stuff, but it seems kind of overkill and something that I don't want to use as the default way of loading these assets, at least not until I know which direction Unity will take for loading .uxml and .uss files.
     
    Last edited: Dec 27, 2019
  4. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    It could be an option to use CallerFilePath, or even do an Assetbundle.FindAssets to the file name you are using at the moment. Its a safe bet the filename wont change, just the path.

    So you use those methods to get an idea where you are, and use File.IO.Path to browse from there. Or even relative to your script path, e.g.
    Code (csharp):
    1.  
    2. string uxmlPath = foundScriptPath + "../template/list.uxml";
     
  5. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    The CallerFilePath works quite fine and is fast, the only downside being that it finds the path directly to the .cs script. Assuming the .uxml and .uss files are in the same directory, this is a good solution.

    I was literally blind not to notice that the AssetDatabase.FindAssets could return the files I wanted. I'll play around a bit and post if it's fast enough.

    EDIT: Yep, AssetDatabase.FindAssets does the trick. Thanks a lot!
     
    Last edited: Dec 31, 2019
  6. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    317
    TheHeftyCoder likes this.
  7. TheHeftyCoder

    TheHeftyCoder

    Joined:
    Oct 29, 2016
    Posts:
    91
    There seems to be a way to do that, at least when you're making a UI for the game with UIElements. I do not know if we will be able to use it for Editor Tools as well.



    I believe what you suggest should be a must feature in the future, would make things a lot easier. Always nicer to work with pure Unity objects rather than magic strings.
     
  8. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    Yes, this works for runtime because the Scene, through the Panel Renderer component, remembers where the StyleSheets and UXML assets are by GUID.

    And within UXML and USS, you can use the `src` attribute to reference files relative to the current file. So you can have UXML reference USS files that are in the same folder by just their name.

    The only remaining issue we have is on the Editor side where you need to load assets by path, at least the main UXML VisualTreeAsset. If you're doing this in an EditorWindow, you can get the path to your script asset for it and then derive a relative path to your UXML asset, but for custom VisualElements this won't work. For those, CallerFilePath (if that works for you) or AssetDatabase.FindAssets() are all options. There's also the Resources folder, which doesn't require absolute paths, and which if you put inside an Editor folder you won't accidentally bloat your player with editor-only assets.
     
    TheHeftyCoder likes this.
  9. LCLapierre

    LCLapierre

    Joined:
    Apr 29, 2019
    Posts:
    15
    Hello,

    Reviving this thread to ask a question about UXML and Addressables. I've tried the following
    Code (CSharp):
    1. m_ConsoleMessageHandle = Addressables.LoadAssetAsync<VisualTreeAsset>("UI/ConsoleMessage.uxml");
    but it returns me null in handle.Result. I made sure that my asset has the correct path of course. Is this not possible yet or is it and I'm doing something not correctly?

    Thanks!
     
  10. LCLapierre

    LCLapierre

    Joined:
    Apr 29, 2019
    Posts:
    15
    Nevermind all of that, removed the .uxml from code and from the asset path in the editor and it works.
     
    uDamian and TheHeftyCoder like this.
  11. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Sorry this is a little old but I'm trying to load a
    .uxml
    file inside a package and this seems to be the best hint towards a solution so far.

    How do you retrieve the path to your script? I've tried
    AssetDatabase.GetAssetPath(wnd)
    and
    AssetDatabase.GetAssetPath(this)
    but they both are just empty, which makes sense in my opinion given that they are not assets. Not sure how you get to the actual script path.

    Thanks for your help :)
     
  12. Kirsche

    Kirsche

    Joined:
    Apr 14, 2015
    Posts:
    121
    I never tested this with package assets but it should work. Try the code below:
    Code (CSharp):
    1. public static T LoadAssetByGuid<T>(string guid) where T : UnityEngine.Object
    2. {
    3.     var assetPath = AssetDatabase.GUIDToAssetPath(guid);
    4.     return AssetDatabase.LoadAssetAtPath<T>(assetPath);
    5. }
    6.  
    7. const string UxmlGuid = "3d5aa598cf88a4f4fbe64c0a6e8eadd8";
    8. var uxmlAsset = AssetDatabaseEx.LoadAssetByGuid<VisualTreeAsset>(UxmlGuid);
    You can find the Guid in the corresponding *.meta file of your Uxml asset.
     
  13. RedHillbilly

    RedHillbilly

    Joined:
    Mar 24, 2014
    Posts:
    39
    Thanks! I saw the workaround to load by GUID, I don't know why but I'm not the biggest fan of hard-coding the GUID and hoping it won't change for whatever reason. It's probably silly and actually the way to go.

    For now I've relied on try and catch:
    Code (CSharp):
    1. try
    2. {
    3.     _visualElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Packages/ch.giezi.tools.githubissuebugreporter/Editor/GieziToolsGithubBugReporterInfoHandler.uxml").Instantiate();
    4. }
    5. catch
    6. {
    7.     _visualElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/GithubIssueBugReporter/Editor/GieziToolsGithubBugReporterInfoHandler.uxml").Instantiate();          
    8. }
    Probably not better though.