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

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!

Why not make it serializable and link it in? Or perhaps load it from a path relative to the current file.

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.

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.

string uxmlPath = foundScriptPath + "../template/list.uxml";

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!

Would be fabulous if Stylesheets showed up as described for objects in this thread

Would let you externally serialize Stylesheets for editor windows and custom VisualElements
:slight_smile:

1 Like

There seems to be a way to do that, at least when you’re making a UI for the game with UI-Toolkit. 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.

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.

1 Like

Hello,

Reviving this thread to ask a question about UXML and Addressables. I’ve tried the following

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!

Nevermind all of that, removed the .uxml from code and from the asset path in the editor and it works.

2 Likes

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 :slight_smile:

I never tested this with package assets but it should work. Try the code below:

public static T LoadAssetByGuid<T>(string guid) where T : UnityEngine.Object
{
    var assetPath = AssetDatabase.GUIDToAssetPath(guid);
    return AssetDatabase.LoadAssetAtPath<T>(assetPath);
}

const string UxmlGuid = "3d5aa598cf88a4f4fbe64c0a6e8eadd8";
var uxmlAsset = AssetDatabaseEx.LoadAssetByGuid<VisualTreeAsset>(UxmlGuid);

You can find the Guid in the corresponding *.meta file of your Uxml asset.

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:

try
{
    _visualElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Packages/ch.giezi.tools.githubissuebugreporter/Editor/GieziToolsGithubBugReporterInfoHandler.uxml").Instantiate();
}
catch
{
    _visualElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/GithubIssueBugReporter/Editor/GieziToolsGithubBugReporterInfoHandler.uxml").Instantiate();           
}

Probably not better though.