Search Unity

Questions about Template.path and schemeLocation in UXML

Discussion in 'UI Toolkit' started by Kichang-Kim, May 14, 2019.

  1. Kichang-Kim

    Kichang-Kim

    Joined:
    Oct 19, 2010
    Posts:
    1,011
    Hi, The title is all, is there any method for using relative path for Template.path?

    The official sample (https://docs.unity3d.com/Manual/UIE-ElementRef.html) 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.
     
    Last edited: May 14, 2019
  2. patrickf

    patrickf

    Unity Technologies

    Joined:
    Oct 24, 2016
    Posts:
    57
    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.
     
  3. DavidG_Serious

    DavidG_Serious

    Joined:
    Mar 7, 2019
    Posts:
    2
    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.
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,231
    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.
     
    DavidG_Serious likes this.
  5. DavidG_Serious

    DavidG_Serious

    Joined:
    Mar 7, 2019
    Posts:
    2
    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)
     
  6. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    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 :

    Code (CSharp):
    1.     public void OnEnable()
    2.     {
    3.         // Each editor window contains a root VisualElement object
    4.         VisualElement root = rootVisualElement;
    5.  
    6.         // VisualElements objects can contain other VisualElement following a tree hierarchy.
    7.         VisualElement label = new Label("Hello World! From C#");
    8.         root.Add(label);
    9.  
    10.         var csScriptPath = AssetDatabase.GUIDToAssetPath("6d690a34d45ba491991385dee51e3c4e");
    11.         var csFileName = Path.GetFileNameWithoutExtension(csScriptPath);
    12.         var csDirectory = Path.GetDirectoryName(csScriptPath);
    13.  
    14.         // Import UXML
    15.         var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{csDirectory}/{csFileName}.uxml");
    16.         VisualElement labelFromUXML = visualTree.CloneTree();
    17.         root.Add(labelFromUXML);
    18.  
    19.         // A stylesheet can be added to a VisualElement.
    20.         // The style will be applied to the VisualElement and all of its children.
    21.         var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>($"{csDirectory}/{csFileName}.uss");
    22.         VisualElement labelWithStyle = new Label("Hello World! With Style");
    23.         labelWithStyle.styleSheets.Add(styleSheet);
    24.         root.Add(labelWithStyle);
    25.     }
     
  7. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Lazy helper method version..

    Code (CSharp):
    1.     public static class UxmlHelper
    2.     {
    3.         private static Dictionary<Type, VisualTreeAsset> cache = new Dictionary<Type, VisualTreeAsset>();
    4.  
    5.         /// <summary>
    6.         /// Loads a `.uxml` at the same location named like the class name of its `.cs` script.
    7.         /// </summary>
    8.         public static VisualTreeAsset TreeOfClass<T>() where T : VisualElement
    9.         {
    10.             var type = typeof(T);
    11.             if (cache.TryGetValue(type, out var found) == false)
    12.             {
    13.                 var foundGuid = AssetDatabase.FindAssets($"{typeof(T).Name} t:MonoScript").FirstOrDefault();
    14.                 if (foundGuid == default)
    15.                 {
    16.                     throw new Exception($".uxml of {type.Name} not found.");
    17.                 }
    18.                 var scriptPath = AssetDatabase.GUIDToAssetPath(foundGuid);
    19.                 var path = Path.GetDirectoryName(scriptPath);
    20.                 var fileName = Path.GetFileNameWithoutExtension(scriptPath);
    21.                 var tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>($"{path}/{fileName}.uxml");
    22.                 cache.Add(type, tree);
    23.                 return tree;
    24.             }
    25.             else
    26.             {
    27.                 return found;
    28.             }
    29.         }
    30.     }
    When using it

    Code (CSharp):
    1.     public class AudioGroupPicker : VisualElement
    2.     {
    3.         public class Traits : VisualElement.UxmlTraits { }
    4.         public class Factory : UxmlFactory<AudioGroupPicker, Traits>
    5.         {
    6.         }
    7.  
    8.         public AudioGroupPicker()
    9.         {
    10.             UxmlHelper.TreeOfClass<AudioGroupPicker>().CloneTree(this);
    11.             var groupField = this.Q<ObjectField>();
    12.             groupField.objectType = typeof(AudioGroup);
    13.         }
    14.     }
     
    Filimindji likes this.
  8. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    @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.
     
  9. trancesilken

    trancesilken

    Joined:
    Oct 8, 2019
    Posts:
    1
    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.