Building Reusable UI Systems in Unity

When developing an application with multiple screens, maintaining a consistent user interface can become challenging. Each new screen introduces more elements to style, behaviours to implement, and code to maintain. Without a consistent approach, even simple changes can require updates across several parts of a project.

While working on an ambulance orientation application for post-secondary paramedicine students, our team encountered this consideration early in development. We knew the application would require several interfaces, including scenario selection, dialogue windows, and an objective tracker. We wanted these interfaces to share a consistent appearance while remaining independent enough to support their own functionality.

To accomplish this, we transitioned from Unity's traditional UGUI system to UI Toolkit.

Moving from UGUI to UI Toolkit

UGUI had been our team's primary approach to developing interfaces in Unity. However, UI Toolkit offered a different workflow, separating interface structure, styling, and behaviour into three distinct technologies:

  • UXML (Unity Extensible Markup Language): Defines the structure and layout of UI elements.
  • USS (Unity Style Sheets): Controls the appearance of UI elements, using a syntax similar to CSS.
  • C#: Handles interactions, application logic, and updates to the interface.

This separation was particularly appealing because we wanted to establish a shared visual style without individually configuring every button, label, and panel.

The transition required some planning. Nobody on the team had previously worked with UI Toolkit, so we dedicated an entire sprint, approximately two weeks, to learning the framework and converting our existing interfaces.

Fortunately, the project was still in its early stages. There wasn't much UI to migrate, which allowed us to experiment with the new workflow before committing to more extensive development.

By the end of the sprint, we had established a foundation for building the remaining interfaces using UI Toolkit.

Centralizing UI Styling with USS

One of our primary goals was to maintain a consistent appearance throughout the application.

Rather than defining the same visual properties independently for every screen, we created a global USS stylesheet containing our shared styles.

Each interface maintained its own UXML file, allowing us to structure screens according to their individual requirements while referencing the same stylesheet.

For example, buttons used throughout the application could share a common USS class:

.button-primary {
    background-color: #2563eb;
    color: #ffffff;
    border-radius: 6px;
    padding: 10px 16px;
}

With this approach, changing a shared property in the stylesheet would update every element using that class. We no longer needed to locate and modify each instance individually.

Not every element could rely exclusively on global styles. Some interfaces required minor adjustments to accommodate their layouts. In those cases, we applied local styling directly within the corresponding UXML.

This gave us a balance between consistency and flexibility. Common visual properties remained centralized, while individual screens could still accommodate their specific requirements.

Separating Interface Logic with C#

Styling was only one part of the architecture. We also needed a consistent approach to handling UI behaviour.

Each screen had a dedicated C# controller responsible for interacting with its UXML elements. This included retrieving references to visual elements, registering callbacks, and updating the interface when application state changed.

Keeping these responsibilities within individual controllers made it easier to work on one interface without modifying unrelated screens.

One example was the objective tracker I developed for the ambulance orientation application.

The tracker displayed tasks that students needed to complete while working through a scenario. As students progressed, the interface needed to reflect which objectives had been completed.

Rather than having the scripts responsible for individual tasks directly manipulate the objective tracker's visual elements, we used an event-driven approach.

When a student completed a task, the application dispatched an event. The objective tracker listened for that event and updated the corresponding task in its interface.

Completed objectives turned green and displayed a checkmark, giving students immediate visual feedback.

This approach kept the systems responsible for tracking task completion separate from the interface responsible for displaying progress.

It also meant that changes to the objective tracker's appearance didn't require modifications to the systems that triggered those events.

Establishing a Better Development Workflow

The transition to UI Toolkit introduced an initial learning curve, particularly because our team was already familiar with UGUI.

Working with UXML and USS required a different way of thinking about interface development. Instead of assembling and styling everything through Unity's traditional GameObject-based UI workflow, we needed to become familiar with defining layouts and styles separately.

However, the benefits became apparent as development progressed.

Having a shared stylesheet helped maintain visual consistency across the application. It also made maintenance easier because common styling changes could be applied centrally.

Separating interface structure, styling, and behaviour improved our development workflow. Developers could work on individual screens while following the same conventions, making the project easier to extend as additional interfaces were introduced.

The two-week investment in learning UI Toolkit was worthwhile for our team. Because we made the transition early, we avoided having to migrate a much larger collection of interfaces later in development.

What I Took Away from the Experience

Working with UI Toolkit reinforced the importance of establishing development practices before a project becomes difficult to maintain.

The biggest benefit wasn't necessarily any individual feature of the framework. It was having a consistent approach to building interfaces.

Shared USS styles reduced duplicated styling, dedicated C# controllers kept screen behaviour organized, and event-driven communication allowed interface elements to respond to application changes without tightly coupling unrelated systems.

UI Toolkit isn't automatically the right choice for every Unity project. UGUI remains a viable option, particularly for projects with established interfaces or requirements that fit its workflow.

For our application, though, adopting UI Toolkit early gave us a foundation that supported the project's growing collection of screens.

The experience also changed how I approach UI development. Before building individual interfaces, I now give more consideration to how they will share styles, communicate with application logic, and accommodate future changes.

- Jordan Partridge