How to create self-contained components (blocks)?

Hello! I have a question about structuring my UI blocks. I have a large experience with fronted and BEM development and trying to setup my code to make it easy to work. My development process only depends on code and I don’t want to use builder at all since it creates more problems to me.
I have some idea how to make it work but they all seems ugly and I want some feedback.

It would be easier if I explain with example. Let’s get a progress bar with label inside (on attached picture). While it is simple it can be a good demonstration for more complex blocks. For this progress bar I want multiple files

  • C# containing a class. Extending VisualElement and providing API to work with. Such as setting value and label
  • UXML containing layout. I want this one instead of creating elements manually in c#.
  • USS containing styles
  • Sprite image for background

Ideally, I want them close in file hierarchy but I can live without it as well.
Now, in a parent block I want to do something like this:

using UnityEngine.UIElements;

public class MyUI : VisualElement {
    public MyUI() {
        var progress = new Progress();
        progress.value = 0.4f;
        progress.label = "40 %";
        Add(progress);
    }
}

Or, add it to parent UXML

<Progress value="10"/>

and then manipulate from parent C#

This approach (if working) is perfect to keep complexity and make things discoverable. It worked well on very large projects 10 years ago.

However, there is an issue with linking.
I’m able to link

  • Image from USS via relative path url(“…/Relative/sprite.png”)
  • USS from UXML via relative

But I’m not able to link UXML from C#. This essentially breaks my concept. What solutions/ideas I have

  • Put all my UXML to Resources and grab them by Resources API. Now it will bundle everything and I need to restructure my assets.
  • Switch to addressables and deal with async initialization in each block. Plus, all manual wiring.
  • Create some global list of template and use them. Maybe link manually via scriptable object. This means manual wiring

What I want to achieve with this post:

  • Evaluate solutions/ideas I have
  • Find better solutions

Would really appreciate any help!

8379807--1104927--Screen Shot 2022-08-21 at 21.13.39.png

I’ve been doing this in a couple of different ways depending on what seems more convenient/flexible for the specific use of the control. These largely feel like compromises for not having a straightforward solution to the exact problem you are describing, though.

Creating the whole element in code

  • Inherit from VisualElement (or another element which provides some baseline functionality you want to extend)
  • In UxmlTraits.Init, add all the child elements to build the needed hierarchy. Assign them hard-coded USS classes, setup any needed event handlers, etc.
  • In any .uss file, specify the default styling using the classes assigned above.

Now you can use the new element both in UXML or by constructing it in code an adding to a document. The downside, though, is if you need to change your hierarchy you can’t do it just be changing some UXML.

Paired .uxml and C# files

  • Create a UXML file describing your hierarchy, classes assigned to each element, etc.
  • Create a C# class which accepts a VisualElement and hooks up all the behavior-side stuff you need.
  • The C# class provides the public API for the element. The UXML gets instantiated at runtime somehow (e.g. adding a VisualTreeAsset to a prefab) and then passed to the “controller” C# class:
public class SomePrefabComponent : MonoBehaviour
{
  [SerializeField] VisualTreeAsset progressUxml;

  void CreateElement()
  {
    progressElement = progressUxml.CloneTree();
    ProgressBarController p = new ProgressBarController(progressElement);
    root.Add(progressElement); // add progressbar to some UI document that exists on-screen
  }
}

public class ProgressBarController
{
  Label percentLabel;
  public ProgressBarController(VisualElement progressRoot)
  {
    percentLabel = progressRoot.Q<Label>();
    progressRoot.Q(...); // query for any other needed elements in UXML to hook up events, set default values, etc.
  }

  float value = 0f;
  public float Value
  {
    get => value;
    set
    {
      this.value = value;
      percentLabel.text = value.ToString();
    }
  }
}

Two major downsides to this approach are: You cannot include this element in a completely functional way just in UXML. You can link the UXML document created above in another, so the layout and everything can still be included that way, and you can keep your UXML documents nicely broken up, but somewhere you will still need to do something like new ProgressController(documentRoot.Q("progress-bar-id")) in order for it to be actually functional. On top of that, if your UXML is complex it will likely mean you need to have a lot of hardcoded element or class IDs in the C# class so it can pick out all of the elements it needs, which makes changing the UXML layout a little more painful. It might not be too crazy to write an editor script which generates a corresponding barebones C# class from a UXML file to auto-include queries for all the elements that have IDs, which could make this a less burdensome solution to maintain.

Using Resources.Load

  • As above, the UXML is created on its own, and located in some Resources directory
  • Anyone who needs it at runtime calls Resources.Load<VisualTreeAsset>("Path/to/UXML").CloneTree();
  • As above, some C# class is still responsible for going through the loaded tree and hooking up any events, default values, etc.

This is the least convenient option, in my opinion, and I only go this route when the UXML that needs to be loaded cannot be known ahead of time (e.g. if the element being loaded is being loaded from a MonoBehavior, I would prefer the second option above over this one).

Hi @noirb ,

Thank you for the response.
I pretty much use the same approaches. I have regular C# classes + Templates. But I don’t like the fact that I need all this wiring now and parent should know about child templates. As you mention this is painful for dynamic elements (like items of the list). And what I really want is to find unified way to write my blocks. Otherwise it would be pain changing from one type to another.

I’m considering just dumping UXML into resources and just live with that.