How to build modular game UI in Unreal Engine 5

Starting tips for avoiding widget spaghetti
Published September 6th, 2023
Jason Tu

When building UI in Unreal, you may be tempted to "hard-code" your entire UI.

For example, let's take a button in your game's pause menu:

pause menu

You might style the Resume button's Button widget and its child Text widget, and pat yourself on the back for a job well done:

  1. Roboto font. Size 48. Regular font style.
  2. 48 for horizontal padding, 24 for vertical padding.
  3. Half-height radius borders for each of the Normal, Pressed, and Hover button states.

The ExitToMenu button then calls for the same exact button, only with different text. Yet, instead of reusing work that you did previously, you labor and run through the tedium of toggling the appropriate settings once more:

  1. Roboto font. Size 48. Regular font style.
  2. 48 for horizontal padding, 24 for vertical padding.
  3. Half-height radius borders for each of the Normal, Pressed, and Hover button states.

Which is fine, I guess.

But imagine a larger chunk of UI, such as an Options widget that allows the player to tweak graphics options:

options menu

What if you wanted to use the entire Options widget twice? Both in your game's start menu and pause menu? Recreating the Options widget twice would be wasteful.

And in a game project — where UI is one among many of a game project's concerns — it helps greatly to reuse work as much as possible:

lots of work

All this UI work, and we haven't even gotten to the game yet!

That is, you can build game UIs with greater ease by keeping reusability front and center.

Much like how web UI developers create UI components (see my blog post on the subject), game UI developers can use the same principle of modularity to make games quickly and effectively.

So let's discuss how we would do so in Unreal Motion Graphics — the UI framework that comes built into Unreal Engine 👇

1. Understand your tools

Any UI developer — whether for web, mobile or game UIs — works with a holy trinity of tools. In Unreal Engine, those tools just happen to have robust visual editors:

RoleWeb UIGame UI
Describe content of UIHTMLUMG UI Designer
Enhance UI with interactivityJavaScriptBlueprint Graphs
Style your UI's look and feelCSSUI Materials

We bring all 3 together within the structure of a component framework.

For web UIs, that might be something like React or Vue. For Unreal Engine, that framework is the UUserWidget class, which you can subclass in order to create Widget Blueprints.

So we want to start creating reusable lego bricks, and we do so by creating Widget Blueprints.

But how do we decide when and where to create them? The answer lies in...

2. Decompose designs into primitives

In any UI/UX workflow, you typically start with a design.

That design could be formalized as high-fidelity mockups in Figma. It could take the form of low-fidelity wireframes. If you're rapid prototyping, you might even forgo a formal design artifact, and simply design as you go.

Working alone, I like the low-fidelity wireframe approach. Here's what the menu flow looks like for my latest game project, Chomp:

Chomp menu flow

From this design, you would then identify the pieces of repeated UI.

Try looking for primitive building blocks first. I might identify the following:

  • Button
  • Heading
  • Paragraph
  • Score
  • HighScore

Then identify more substantial building blocks that have reuse potential, such as:

  • Dialog
  • OptionsSelector
  • OptionsMenu

Remember that building blocks aren't limited to the content themselves. They can also be the container, as in the case of a Blur widget whose blur strength you'd like to keep consistent:

blur

Within the Blur widget's hierarchy, you can expose a "Named Slot" widget that allows users of the Blur widget to customize the "Named Slot" contents:

hierarchy

In this example, "WBP Blur" is our Blur widget and BackgroundBlurContent is the "Named Slot".

3. Expose variables for data and styling

To foster reusability, our UI primitives should allow for customization.

For example, a Heading widget might expose a TextValue variable for customizing the Heading's contents.

To mimic the way web headings work, it might also expose a Size enum variable:

UENUM(BlueprintType)
enum class EHeadingSize : uint8
{
    H1,
    H2,
    H3,
};

As well as a Color variable, to restrict the set of possible text colors to a desired color palette:

UENUM(BlueprintType)
enum class EHeadingSize : uint8
{
    Red,
    Blue,
    Pink,
};

By initializing our text block with the exposed variable settings through the Pre Construct event in Blueprints:

preconstruct event

Showing only one setter for the sake of clarity.

We can easily customize any instance of the Heading through the Heading widget's Details panel:

heading widget

4. Expose event dispatchers for custom behavior

Widgets aren't necessarily static pieces of content.

They have behavior (such as buttons) that perform actions, or otherwise modify the state of the game in some way.

When we want to customize this behavior, we can follow the Hollywood principle (don't call us, we'll call you) and expose Blueprint Event Dispatchers in our Widget Blueprints.

(If you're familiar with React, you may recognize this pattern as passing a function down.)

button event dispatcher

Exposing an `OnButtonClicked` event dispatcher from our `Button` widget.

Users of your Widget would then hook into these Event Dispatchers to inject custom behavior:

resume event dispatcher

React web developers will see the all-too-familiar callbacks if they squint hard enough. 😂

For other widgets where you want to encapsulate behavior instead of customizing, you might choose to forgo exposing event dispatchers entirely. Here's my implementation of a HighScore widget, where the high score data needed to be fetched from the SaveGame file:

update text function inside high score widget

Encapsulating data fetching within a Blueprint function that is private to the HighScore widget.

But ultimately, whether it's for data, styling, or behavior, you make the final judgment call on reusability.

You might make the call based on repeated occurrences in your design, as we discuss in this blog post. Or you might make the call based on convenience, or expected future need.

The important thing is to do what makes the most sense for your development circumstances and accrued time budget in the long run.

5. What about the look and feel?

The great thing about creating a set of reusable widgets is that you can now work on your game's look, feel, and animation in a piecemeal fashion. It may be intimidating to dress up an entire screen of UI, but a single button is far more approachable.

The UI Material Lab is a fantastic resource for learning how to jazz up your UI's look and feel:

UI material lab

Takeaways

So we’ve walked through a 3-step process for building reusable game UI:

  1. Decompose designs into primitives
  2. Expose variables for data and styling
  3. Expose event dispatchers for custom behavior

The end result? A set of Widget Blueprints that is modular, composable, and consistent. A design system for your game that you can improve and grow over time:

your game's design system

It becomes a joy to construct UI into hierarchies of robust building blocks, and your player benefits from the consistent UX throughout your game.

If you're starting out, I hope this brief guide has equipped you with the right mindset for constructing your game's UI. As always, until next time!

Resources

Here are some resources for learning more about modular UI construction:

If you liked this post, you'll like this too.

I publish blog posts just like this one when I have something to say.

I rarely send out emails, but if you enter your email below, you'll get an email if I decide to announce similar blog posts.