August 16, 2017

Creating a Data-Driven UI with UMG

By Cody Albert

When implementing a game UI, we often don’t think much about maintainability. We receive a mockup from an artist, break it out into individual elements, place our widgets on the screen with placeholder art and drop in the final assets when they’re ready. This works great for elements such as a HUD or a menu that doesn’t require much iteration, but what do we do when we need to expose a more complex system? Rather than make constant modifications to the UI every time a new item gets added or a new menu option is required, we can set up a data-driven UI to remove ourselves from the pipeline, coupling the underlying data directly to the interface automatically.
UnrealEngine%2Fblog%2Fcreating-a-data-driven-ui-with-umg%2FDynamicUI_Fortnite_770-770x480-38b0abba26d654c36e5a4c02ebcb9314533cd6d8

DATA-DRIVEN UI

A data-driven UI element is one which is constructed procedurally based on some underlying data source instead of being built by hand. The beauty of this pattern is that a designer could make changes to the system being exposed by the UI without having to make any adjustments to the UI itself. The biggest drawback is that since the element only exists at runtime, it can be difficult to preview and finely control how it will appear in the game.

For example, imagine a shop in a game. Our interface needs to show a list of all available items for purchase with their price and an icon. It wouldn’t be too difficult to build a window with all of this information, but what if a designer wants to add or remove items from the shop? What if the prices need adjustment or icon art needs to be updated? All of these would require modifications to the interface, and forgetting to do so could cause the interface to fall out of sync with the data. Nobody wants to purchase an item in a game listed at 500 gold only to find 1000 gold taken out of their inventory!
In this post, I’m going to describe how to set up a data table and couple it to a shop widget which will display an arbitrary number of items in a scrolling list. I’ll also show how to broadcast events when selections are made and talk a bit about how we can expand upon these ideas to fit the needs of your project.

GETTING STARTED

The most important part of any data-driven UI element is the data itself, so let’s set up a data table to contain our shop’s inventory. First, we’ll need to create a struct that represents the columns that each row of our table will contain. Create a struct by clicking Add New, opening the Blueprints category, and clicking Structure.

I’ve set mine up like the image below:
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven1-750x105-63d44c3d94c993eedee52f540a7df3b41e1e0eda
Note that I didn’t create a Name field, as the data table will automatically add this in later. We’ll use the name if we ever need to look up a specific entry in the table.

Next, we’ll create the data table itself by clicking Add New again and expanding the miscellaneous category. Select the struct you created previously, and then start adding in some entries!
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven2-379x232-261fcfc757bda6129374dbe2eb29611e859744ae
Now that we’ve got our data, we need to create two widgets. First, we’ll need our main shop window, which we’ll create whenever the shop is available. Then, we’ll make an ItemRow widget, which will be created at runtime to represent a single row of our data table.

Let’s start with the ItemRow. Our goal is to create a generic widget that can be duplicated and populated automatically, with a layout that can adjust to fit the different text lengths and such that we allow.

I’ve made some assumptions about maximum text size and icon size, but here’s my widget:
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven3-750x267-e111de989c968bab6c7ef4151e1adf264cbce800
Note that I’ve deleted the starting canvas panel from the top of the hierarchy, as this widget will be positioned and sized by its parent.

I also created a SetValues function to simplify feeding the data into my template. I could have used property bindings for a quick and easy solution, but bindings will update the value every tick. In any situation where performance may be a concern, it’s best to avoid bindings and instead, set up events to only update your properties when needed.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven4-750x162-93d460524beece671d753d1f0392f0e5920ac5fd
Next up, we’ll create the main widget which will hold our entries. This widget will contain the Blueprint for reading in the data table and creating an ItemRow widget for each row. We can temporarily drop some ItemRow widgets in to visualize, but we’ll want an empty scroll box in the final product since the rows will be added by the Blueprint.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven5-750x410-78fd40a32f7d2322cfceead631967be6ad5278c6 UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven6-750x138-f7777971604338d1427b71ffcebc27bd02e97dab
Now we just have to add our main widget to the viewport, and it’ll fill up with all of the latest info from the data table! If we want to add, remove, or change any of the entries, all we have to do is update the data table.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven7-750x693-9f5d94a0bd2994a87205c191c88a18d05168a71a

MAKING SOME IMPROVEMENTS

The first annoyance you may have noticed is that you have to actually start PIE to view your shop interface, which means making adjustments may come down to trial and error. Luckily, if you’re on 4.15 or later, we’ve added in an Event Pre Construct node for widget Blueprints. This is essentially the same as the Construct node, except it’ll run at edit time so we can see how the widget will look in our preview viewport without hitting play. Simply disconnect the Blueprint we used to build the rows from the Construct node and attach it to the Event Pre Construct node.

Be aware that if you modify the data table, you’ll need to hit Compile to rebuild the preview widget, even if the button is still showing the usual checkmark.

It’s also important to keep in mind that although the nodes connected to Event Pre Construct will still be called at runtime as before, they’re now being called within the Blueprint editor on the preview widget as well. You’ll want to stick to cosmetic code, and avoid referencing anything that isn’t going to exist until runtime.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven8-448x385-d167cd1eb3372b4815bf7907e3bed63cb6e2d690
Another consideration is that even though we’re currently displaying an arbitrary list of items, we’ll need a way of informing other game systems (such as a character controller) when a button was clicked so that we can deduct the gold and add the item to the player’s inventory. To keep things as modular as possible, I like to create an event dispatcher on my top level widget to act as an intermediary between the individual entries and anyone who wants to receive events when a particular button is pressed. Your ItemRow will need a handle back to your root widget so that it can fire the event, which you’ll call by overriding your ItemRow’s OnMouseButtonDown.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven9-1117x289-21ea993e6cd9611cbfdbc9c7c8fce7c071dfb511
In the ItemRow Widget.

Any interested parties can bind a custom event to the root widget’s event dispatcher, receive the name of the clicked item as a parameter, and do their own lookup in the data table for the full details.
UnrealEngine%2Fblog%2Fstatic-analysis-as-part-of-the-process-copy%2FDataDriven10-750x300-1d1f174e2d03a7a0d07e96abfd67540c9b43ff9b
In your main, top-level widget.
 
It’s also important to understand that the data table used in my example is meant to be modified only at edit-time. There may be cases in which you need a more dynamic data structure which can be modified during the course of your game. You may even want to use a data table as the starting state for your data, copying it over to another structure which you can then modify as needed. For maximum versatility, you can create a data structure with arbitrary properties and some metadata that tells the UI how to display those properties. It’s worth taking some time upfront to decide which solution will work best for you, rather than run into unexpected limitations down the line.

FINAL THOUGHTS

While some UI elements don’t change enough to warrant the extra time and effort needed to set up a data-driven widget, many systems can benefit from the flexibility they afford. Rapid iteration is pivotal to good game design, and generating an interface procedurally lets you go from idea to execution in no time at all.