This post is about best practices when dealing with large UI systems in UMG. I have worked as a UI programmer on two major games for over 5 years. This is based on my experience, working on a large RPG that I composed mostly in Blueprints and a citybuilder that has almost no logic in Blueprints.

Logic in C++, Visuals in Blueprints

Writing logic in Blueprints has some benefits and drawbacks:

  • Simpler for people with no experience of programming.
  • Faster iteration than C++ in general.
  • Text diffs, resolves and merges are impossible in Blueprints.
  • Blueprints must be checked out to avoid merges between team-members. So only one team member can work on a widget at any one time.
  • Slower execution than C++.
  • Larger systems become very hard to understand and debug in my experience.

If you are not comfortable in C++, definitely stick to blueprints, but otherwise I would strongly recommend writing logic in C++ and using blueprints just to lay out widgets.

I write all my logic in C++, and use Blueprint subclasses just for laying out the widgets. It is possible to define UIs using Slate in C++ but it is far slower than using UMG and the Unreal Editor. Using meta=(Bindwidget) makes it very easy to set the state of widgets from C++.

I consider visual state changes part of logic, so I control animations from C++ and show/hide widgets using C++.

Naming Conventions

Unreal has many of naming conventions for UObjects, AActors, enums, structs. This is explained pretty well in the manual. .

Allar's Style Guide is also great for naming conventions for assets.

I'd like to talk about UI-specific naming conventions.

Project-wide Class Prefix

I got the habit of adding a project or company-wide prefix for all code made in-house. I find it very useful to know what classes are "ours" and what are "Unreal's".

For example if you work at Benui Co. you might prefix all your classes with BUI. A dog actor would be ABUIDog, its ball would be ABUIBall. It takes a while to get used to but it avoids naming clashes (I don't use namespaces in Unreal).

For widgets, UButton is Unreal's built-in button widget, and UBUIButton would be an in-house Button class.

C++ UserWidgets

I like to add UW in my UUserWidget subclasses. So a health bar class would be:

U + BUI + UW + HealthBar = UBUIUWHealthBar

It looks pretty egregious but I find it especially useful for differentiating between UWidgets and UUserWidgets:

  • UBUIButton would be a custom UButton subclass
  • UBUIUWButton would be a UUserWidget subclass that defines a button with extra features like an icon, label, keyboard shortcut display.

Blueprint Prefix

I use the prefix WBP_ ("widget Blueprint") for widget Blueprint subclasses. I also make sure the Blueprint name matches its C++ parent.

To continue the health bar example a concrete example of a health bar class:

  • UBUIUWHealthBar is the C++ class that defines the logic the health bar.
  • WBP_HealthBar is the widget blueprint that inherits from UBUIUWHealthBar, and defines its appearance.

Naming Conventions Within Blueprints

This is completely subjective, but I find it very helpful to have a consistent naming convention for widgets within Blueprints.

It helps remembering what type of widget you are dealing with when in C++ code, and makes it easier to find widgets in the editor.

I use the following suffixes for widgets:

  • Don't give custom names to widgets that have no C++ binding
  • Label suffix for UTextBlock (this could be Text but I find it confusing with FText).
  • Image suffix for UImage
  • Button suffix for UButton
  • Panel suffix Overlays, Canvas Panels, Horizontal Boxes, Vertical Boxes. I merge these because I often want to change how a widget is arranged and I don't want to rename everything.
  • Switcher suffix for WidgetSwitcher

Create a Base UUserWidget Class

Most Unreal UI tutorials (including those on this site) at first teach you to create C++ subclasses of UUserWidget. Leading to a hierarchy

UUserWidget
 β”œ UBUIUWHealthBar
 β”œ UBUIUWMenuPage
 β”‚  β”” UBUIUWMenuSettingsPage
 β”” UBUIUWInventoryItem

Instead, create a subclass of UUserWidget and make all your classes inherit from that:

UUserWidget
 β”” UBUIUserWidget
    β”œ UBUIUWHealthBar
    β”œ UBUIUWMenuPage
    β”‚  β”” UBUIUWMenuSettingsPage
    β”” UBUIUWInventoryItem

Doing this makes it extremely easy to add functionality to all your UserWidget subclasses in a single place.

Create Custom Base Classes for All Widgets

Similar to the example above, I would strongly recommend creating custom widget base classes for most core widgets, and using those in your UUserWidget Blueprints.

At first, these can just be dummy subclasses of the standard UMG widgets:

#pragma once

#include "CoreMinimal.h"
#include "Components/Button.h"
#include "BUIButton.generated.h"

UCLASS()
class EXAMPLE_API UBUIButton : public UButton
{
	GENERATED_BODY()
};

As your project evolves you can add more functionality to UBUIButton by overriding virtual functions in UButton and adding your own delegates. Eventually if needs be, you can change UBUIButton to not inherit from UButton at all, and write something completely from scratch.

The point is if you have been doing this from Day 1, all your Blueprint widget classes will have UBUIButton instances within them, so adding functionality will just be changing a few lines of code, and not going through hundreds of Blueprint files.

How many core elements you want to subclass is up to you. I would suggest starting with at least UButton, UImage and UTextBlock.

Our beautiful custom button class

Create Reusable UserWidget C++ Classes

Using the naming conventions described above, I have found it incredibly useful to create a general-purpose Button UserWidget class into which I can add many things.

UCLASS()
class UBUIUWButton : public UBUIUserWidget
{
public:

protected:
	// Not all blueprint subclasses of this may want to have a text label
	UPROPERTY(meta=(BindWidgetOptional))
		UTextBlock* MainLabel;

	// As described above, this is a custom Image widget
	UPROPERTY(meta=(BindWidgetOptional))
		UBUIImage* MainImage;
		
	UPROPERTY(EditAnywhere)
		bool bIsToggleButton;
	UPROPERTY(EditAnywhere)
		bool bIsToggleOn;
		
	UPROPERTY(EditAnywhere)
		UButton* MainButton;
};

Using this it is simple to create buttons with the same functionality but different appearances by creating different UserWidget Blueprint subclasses of UBUIUWButton.

Different Blueprint subclasses of the same C++ UBUIUWButton class

For more complex buttons with more widgets, it's simple to subclass UBUIUWButton.

For example if we wanted to make a button in a shop for purchasing an item. We could show its price, where it would be equipped on the player by adding more member variables. All of the standard button functionality defined in UBUIUWButton is still there.

UCLASS()
class UBUIUWShopItemButton : public UBUIUWButton
{
public:

protected:
	UPROPERTY(meta=(BindWidgetOptional))
		UTextBlock* PriceLabel;
	UPROPERTY(meta=(BindWidgetOptional))
		UImage* InventorySlotImage;
};

Don't use Bind Variable

If you've used UMG for a while, you've probably noticed the "Bind" buttons next to some Widget properties. These allow you to create a custom function that is called each frame, and returns the value to be used in the property.

Bind is not a great idea

This functionality can seem very useful; they give you ultimate control over values, and you don't have to put anything in Event Tick to make sure the variables are updated.

However they have a few major drawbacks:

  • They are called every frame which is often not what you really need. If the logic within the bound function is even slightly complicated they can become real performance hogs.
  • The "Find references" function of the editor does not show where bound functions are called from. On larger UserWidget this makes them extremely difficult to track down.
  • All the drawbacks of Blueprints mentioned in the first section.

Instead you should put function calls within your NativeTick, or even better change them to an event-based setup so they are only called on state-changes.

Posted: