AO

Procedural UI in Unity Part 1

In my company, we are using Unity for our enterprise software. In the Pre-AI era, it was easier to create software with great 3D capabilities using existing game engines. With our bias towards C#, Unity was the best option out there.

The Problem

Not really sort of a problem, but an increasingly repetitive task that we had. It was to create new UIs everytime we add new features to the software.

We are still using Unity UI at this time, and we wanted to streamline this by focusing on the functionality of the feature more than altering and composing UIs.

Widgeting

Creating widgets was a way to make things easier. The idea is to list down UI primitives and compose it to a much larger and complex UI later on.

UI primitives in a sense is like primitives in C#:

public class FloatWidget : Widget<float>
{
   //Unity UI functionality here
}

for a more complex widget such as Vector2/3

public class Vector3Widget : Widget<Vector3>
{
   public FloatWidget X;
   ...
}

Which probably is a standard way of doing things especially when you have a lot of UIs to work with.

Obviously, a more concrete feature would mix and match these widgets in another view class and hence, the final form of the UI.

However, thats just solving half of the problem. Composing UIs is great, but what if you had like a 100 things you need to compose?

Defining UI using models and metaprogramming

Models in a sense would be a great place to indicate how UIs should look. These classes basically contain half of the data needed in order to define what a UI would look like:

public struct User 
{
   public string Name;
   public int Age;
   ...
}

Looking at the data types, we can easily identify what kind of widget these fields would need in order to render it. Luckily, we have reflection in C#.

FieldInfo provides the type of the member, and voila! We now have a list of widgets just based on the model class.

Expanding with attributes

Relying on the data type of the memeber isn't quite extendable, what if we have variations of float widgets? Such as input fields, a range, or with a clickable button?

Then we need to add more information to the member in order to provide that:

public struct User 
{
   [UI(typeof(InputFieldWidget)]
   public string Name;
   [UI(typeof(IntFieldWidget)]
   public int Age;
   ...
}

and similar to getting the type, we can get the custom attribute that is tied to the member, and get more information.

This is a powerful tool if we want to create a procedural UI without the need to manually create each any every UI that the project needs. We only simply need to provide the data model to a ui builder, and it brings back a set of UI based to your liking.

Reflection is really slow, I would be careful on using them. Ideally, reflections should be pre-cached in order to maximize the performance in it.

That's just defining the UI. I still want to expand on this topic by going over automatic event bindings. Not only that we want to automate creating UIs, we would also want a way to automatically update the data model using the created UI.

Eventually the final form will take place, a working, procedural UI Graph. At least that's the dream. For now, sleeping it is!