Questions about Template.path and schemeLocation in UXML

Hi, The title is all, is there any method for using relative path for Template.path?

The official sample (Unity - Manual: UXML elements reference) used full path like this:

But generally, reusable templates need to be packaged and easily changed its path. Also Custom Packages are located in Packages folder, not Assets.

So, questions are:

  1. Can I use relative path?
  2. Can I use templates in Packages folder?

Thanks.
Edit:
3. Is it impossible to set project root relative path for schemaLocations? It seems that current auto-generated uxml has self-relative path for scheme path, so if the uxml file is moved to other folder, it doesnt work.

Hi, currently, all your templates need to be placed under the Assets folder. You can not specify relative paths.

As for schema location path, they need to be updated when the UXML file move. However, they are there for use by text editors only. Unity does not used the schema files to validate the UXML.

Is there a plan to change this requirement?

Not being able to use templates in packages complicates work I’m trying to do right now.

This is no longer a requirement. You can have templates in a package (many already do) and reference them via the Packages/com.my.package.name/... path, as an absolute path.

References from within a UXML file to other UXML files or USS files can now also be relative, using the src= attribute (instead of the old path= attribute).

The only remaining issue is how the AssetDatabase.LoadAssetAtPath() works, for the initial load of a UXML template in C#. This still has to use an absolute path. There are some tricks to make this dynamic in an EditorWindow or MonoBehaviour, by getting the path to the script asset and deriving the UXML path from that. But the absolute path works in most cases just fine.

Finally, you always have the option to use the Resources folder and the Resources.Load() API. If you put the Resources folder inside an Editor folder inside your package, you won’t incur the runtime cost. You just have to be careful to create somewhat unique paths within the Resources folder because all Resources folders share the same virtual “root” path.

1 Like

Thanks, I’ve gotten it working as described. I appreciate the in-depth reply. I was lead astray by the error message as it doesn’t hint that package relative paths are possible!

Invalid AssetDatabase path: /SandboxWindow_Package.uxml. Use path relative to the project folder.
UnityEngine.UIElements.VisualTreeAsset:CloneTree(VisualElement)

I found that absolute path does not work well when you are authoring the package, since the package is now in Assets/ when you are making it and “packages/com…” will not work. So in the end I use the absolute path derivation of uxml+uss from cs script file :

    public void OnEnable()
    {
        // Each editor window contains a root VisualElement object
        VisualElement root = rootVisualElement;

        // VisualElements objects can contain other VisualElement following a tree hierarchy.
        VisualElement label = new Label("Hello World! From C#");
        root.Add(label);

        var csScriptPath = AssetDatabase.GUIDToAssetPath("6d690a34d45ba491991385dee51e3c4e");
        var csFileName = Path.GetFileNameWithoutExtension(csScriptPath);
        var csDirectory = Path.GetDirectoryName(csScriptPath);

        // Import UXML
        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{csDirectory}/{csFileName}.uxml");
        VisualElement labelFromUXML = visualTree.CloneTree();
        root.Add(labelFromUXML);

        // A stylesheet can be added to a VisualElement.
        // The style will be applied to the VisualElement and all of its children.
        var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{csDirectory}/{csFileName}.uss");
        VisualElement labelWithStyle = new Label("Hello World! With Style");
        labelWithStyle.styleSheets.Add(styleSheet);
        root.Add(labelWithStyle);
    }

Lazy helper method version…

    public static class UxmlHelper
    {
        private static Dictionary<Type, VisualTreeAsset> cache = new Dictionary<Type, VisualTreeAsset>();

        /// <summary>
        /// Loads a `.uxml` at the same location named like the class name of its `.cs` script.
        /// </summary>
        public static VisualTreeAsset TreeOfClass<T>() where T : VisualElement
        {
            var type = typeof(T);
            if (cache.TryGetValue(type, out var found) == false)
            {
                var foundGuid = AssetDatabase.FindAssets($"{typeof(T).Name} t:MonoScript").FirstOrDefault();
                if (foundGuid == default)
                {
                    throw new Exception($".uxml of {type.Name} not found.");
                }
                var scriptPath = AssetDatabase.GUIDToAssetPath(foundGuid);
                var path = Path.GetDirectoryName(scriptPath);
                var fileName = Path.GetFileNameWithoutExtension(scriptPath);
                var tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{path}/{fileName}.uxml");
                cache.Add(type, tree);
                return tree;
            }
            else
            {
                return found;
            }
        }
    }

When using it

    public class AudioGroupPicker : VisualElement
    {
        public class Traits : VisualElement.UxmlTraits { }
        public class Factory : UxmlFactory<AudioGroupPicker, Traits>
        {
        }

        public AudioGroupPicker()
        {
            UxmlHelper.TreeOfClass<AudioGroupPicker>().CloneTree(this);
            var groupField = this.Q<ObjectField>();
            groupField.objectType = typeof(AudioGroup);
        }
    }
1 Like

@5argon you don’t author the package under Assets/.
you embed it i.e. put it physically under Packages/..../ and the paths are the same.
it is fully editable as if it were under Assets/, and if it’s not you report a bug for it.
plus is treated as a package for validation purposes (e.g. no scripts without asmdefs allowed)

you can avoid the AssetDatabase call (in an editor window) by having a serialized field and assigning the asset as a default reference in the script inspector.

I know that this is kind of old but still applies to 2021.2.19f1 and up

To get rid of the maintenance of the relative path in xsi:noNamespaceSchemaLocation you can use

xsi:noNamespaceSchemaLocation=“/…/UIElementsSchema/UIElements.xsd”

At least in Jetbrains Rider, this will work. YMMV.

I’d suggest that the various wizards/code generators in unity, that will generate uxml files from templates, should make this a default.