It seems there are many ways to use UI Toolkit and wondering what people here tend to do.
I’ll break my question into multiple parts. The first is how do you like to build the UI?
It seems there’s 3 options:
In Editor via the UI Builder.
Directly manipulating UXML / USS in a web development style.
Dynamically creating all elements via C#.
Personally I’m stuck on UI Builder. I can see the potential of C#, but I like the immediate feedback. I suppose in the future I would use UI Builder as a prototyping tool and C# when things get more serious.
The next question is how do you like to handle the control flow of the UI?
Are you using a state machine?
Command pattern?
Manual Manager class?
Finally, how do you like to interface with the UIDocument?
Are using a single component to handle the UI Document?
Are using multiple components to handle different parts of the UI?
Are you doing something else by using SOs or POCOs?
For building the U, I do a mix of UI Builder and generating elements in C#. Namely, I usually have a root [UxmlElement] that I plonk into the builder, and everything under that is generated via code. Then I can easily style the elements - and see how they will look - with USS styles and Theme Style Sheets (TSS). You also get immediate feedback as you can simply change some code, recompile, and see your changes in the builder.
I namely came to this process as it nearly eliminates the need to Q<T> for visual elements, as references are established in an elements constructor instead, or via runtime. It also circumvents the pesky TemplateContainer visual element you get when using templates that often mess up your layout. As you’re generally making lots of custom visual elements, it’s easier to control your UI as well.
I think this really comes down to the type of UI in question. Most UI in my current project is simple enough that one class can manage it’s behaviour. But more complicated ones, say, a character customisation screen or a crafting interface probably needs something like a state machine of shorts to handle its various modes.
Either of the two depending on the complexity of the UI.
Nice, I think I will also start dynamically creating elements with C#. UI Builder is cool but it’s quickly becoming obvious that it’s going to be annoying to do dynamic content.
Yeah I’m not sure how you’d do some things without a certain amount of coding, especially as we can’t hook up things like button callbacks via the builder yet. Nor can we reference other visual elements in the builder, though I believe wanting to do that is using UI Toolkit incorrectly.
Which is to say, I generally take a model-view approach with my UI. The UI just displays and sometimes mutates a blob of data, but is otherwise separate. So much so, that I keep my UI code in a separate assembly definition to the non-UI runtime code.
Works very well with the recently added runtime bindings as well.
Is it possible to reference the default assembly? or do I have to create an asmdef for my main project as well? At the moment my UI Scripts in the UI Assembly can not reference any namespaces in the Assembly-CSharp (i.e. everything else)
Yeah the reason that happens is that the default Assembly-CSharp assembly references all user assemblies, as per the diagram in the docs: Unity - Manual: Assembly definitions
If you’re organising your code with assembly definitions, all your code needs to be organised with them. This can be difficult introducing it into a project halfway. It works best when done from the outset.
I did try to go the procedural route, maybe it’s just my background as a UI designer but I found it really difficult to iterate. I felt that my code became quite ugly and heavy to read compared to the UXML/USS paradigm.
Here’s a bad example of worse case scenario.
using UnityEngine;
using UnityEngine.UIElements;
public class NestedUIExample : MonoBehaviour
{
private void OnEnable()
{
// Get the root of the UI document
VisualElement root = GetComponent<UIDocument>().rootVisualElement;
// Create a parent container (vertical layout)
VisualElement parentContainer = new VisualElement();
parentContainer.style.flexDirection = FlexDirection.Column;
parentContainer.style.paddingTop = 10;
parentContainer.style.paddingBottom = 10;
parentContainer.style.paddingLeft = 10;
parentContainer.style.paddingRight = 10;
parentContainer.style.backgroundColor = new Color(0.2f, 0.3f, 0.4f, 1f); // Dark blue-ish background
parentContainer.style.borderTopLeftRadius = 10;
parentContainer.style.borderBottomRightRadius = 10;
// Create a nested container (horizontal layout)
VisualElement nestedContainer = new VisualElement();
nestedContainer.style.flexDirection = FlexDirection.Row;
nestedContainer.style.paddingTop = 10;
nestedContainer.style.paddingBottom = 10;
nestedContainer.style.paddingLeft = 10;
nestedContainer.style.paddingRight = 10;
nestedContainer.style.backgroundColor = new Color(0.3f, 0.4f, 0.5f, 1f); // Slightly lighter blue
// Create labels and style them
Label label1 = new Label("Nested Label 1");
label1.style.fontSize = 16;
label1.style.color = Color.white;
label1.style.paddingBottom = 5;
Label label2 = new Label("Nested Label 2");
label2.style.fontSize = 16;
label2.style.color = Color.white;
label2.style.paddingBottom = 5;
// Create buttons and style them
Button button1 = new Button(() => Debug.Log("Button 1 clicked"));
button1.text = "Click Me 1";
button1.style.fontSize = 18;
button1.style.color = Color.white;
button1.style.backgroundColor = new Color(0.1f, 0.6f, 0.8f, 1f); // Light blue
button1.style.marginBottom = 5;
button1.style.paddingLeft = 10;
button1.style.paddingRight = 10;
Button button2 = new Button(() => Debug.Log("Button 2 clicked"));
button2.text = "Click Me 2";
button2.style.fontSize = 18;
button2.style.color = Color.white;
button2.style.backgroundColor = new Color(0.1f, 0.6f, 0.8f, 1f); // Light blue
button2.style.marginBottom = 5;
button2.style.paddingLeft = 10;
button2.style.paddingRight = 10;
// Add labels to the nested container
nestedContainer.Add(label1);
nestedContainer.Add(label2);
// Add the nested container and buttons to the parent container
parentContainer.Add(nestedContainer);
parentContainer.Add(button1);
parentContainer.Add(button2);
// Add the parent container to the root
root.Add(parentContainer);
}
}
So instead of been instantiating Visual Tree Assets and appending them to the main ui docemnt, and yes, adding a funny step to ensure the template container works.
I’ve been going back and forward for a couple of days and just going to move forward for now, trying to do it in a way where procedural generation is interchangeable, but yea it’s difficult.
But actually, I did have a thought. If you make your Templates Custom Controls instead, you can see them directly in the ui builder and they’re instantiated with C# code? I wonder if that’s a bit naughty or a good idea
I only use code to generate/structure the visual elements. Styling is still done through USS style sheets or TSS Theme Style Sheets. I don’t style the visual elements via code.
I agree with spiney about only using the c# approach to generate structrure, and rely on USS for styling (when you inline the styling it will prevent you to override it from USS, and this is normally undesired).
But either way wanted to recommend you using object initializers when assigning properties that would at least make it much more concise and pretty
var parentContainer = new VisualElement()
{
name = "my-parent-container",
style =
{
flexDirection = FlexDirection.Column,
paddingTop = 10,
paddingBottom = 10,
paddingLeft = 10,
paddingRight = 10,
backgroundColor = new Color(0.2f, 0.3f, 0.4f, 1f), // Dark blue-ish background
borderTopLeftRadius = 10,
borderBottomRightRadius = 10,
},
};
Only issue I have with USS is just resolving the files. If I’m making plugins that can make it risky if people are moving around folders and assets. If it’s embedded in the custom element at least I know I can’t lost it!
Not very flexible, but for small concise elements I think it’s fine. I still find my self prototyping in the ui builder for fast iteration before converting to C#.
I create UXML and USS templates for different views/modals and use a view manager (similar to a state machine) to handle view switching. I have a base UI document that I add and remove the templates to. I use code generated visual elements when I need to, for things like generating a table of players in a multiplayer lobby. To dynamically update elements like text, I just use the Q selector to get the reference though I should probably use the data bindings, but the docs made it seem like a lot more effort to implement. I use scripting to add/remove USS classes for transitions. It isn’t a perfect method but for me it works well and it’s fairly quick to create new views.
I don’t use the UI builder, I work in web dev so I feel more comfortable writing the markup and seeing it update in realtime using the UITK debugger.