How to properly extend UUserWidget for UMG

I’m trying to have my GameMode switch between multiple UserWidgets. The GameMode holds pointers to all of my custom widgets, and there’s a blueprint-callable function that takes an Enum and then swaps the pointer to the correct Widget. Code is below:

ViewGameModeBase.h

UENUM(BlueprintType)
enum WidgetSelector { MAIN_MENU, THREE_OPTION_MENU, TWO_OPTION_MENU, CONTENT_MENU };

UCLASS()
class VIEWER_API AViewerGameModeBase : public AGameModeBase {
    GENERATED_BODY()

   public:
    /** Remove the current menu widget and create a new one from the specified class, if provided. */
    UFUNCTION(BlueprintCallable)
    void ChangeMenuWidget(WidgetSelector selector);

   protected:
    /** Called when the game starts. */
    virtual void BeginPlay() override;

    UFUNCTION(BlueprintCallable)
    void changeWidget(UUserWidget* widget);

    /** The widget instance that we are using as our menu. */
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UUserWidget* mCurrentWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UMainScreenWidget* mMainScreenWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UThreeOptionWidget* mThreeOptionWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UTwoOptionWidget* mTwoOptionWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    UContentScreenWidget* mContentScreenWidget;
};

ViewGameModeBase.cpp

// Copyright Epic Games, Inc. All Rights Reserved.

#include "GameFramework/GameUserSettings.h"
#include "ViewerGameModeBase.h"
#include "Viewer.h"

void AViewerGameModeBase::BeginPlay() {
    Super::BeginPlay();
    UGameUserSettings* gameSettings = GEngine->GetGameUserSettings();
    gameSettings->SetScreenResolution(FIntPoint(1440, 960));

    mMainScreenWidget = CreateWidget<UMainScreenWidget>(GetWorld(), UMainScreenWidget::StaticClass());
    mThreeOptionWidget = CreateWidget<UThreeOptionWidget>(GetWorld(), UThreeOptionWidget::StaticClass());
    mTwoOptionWidget = CreateWidget<UTwoOptionWidget>(GetWorld(), UTwoOptionWidget::StaticClass());
    mContentScreenWidget = CreateWidget<UContentScreenWidget>(GetWorld(), UContentScreenWidget::StaticClass());

    ChangeMenuWidget(WidgetSelector::MAIN_MENU);
}

void AViewerGameModeBase::changeWidget(UUserWidget* widget) {
    if (mCurrentWidget != nullptr) {
        mCurrentWidget->RemoveFromViewport();
        mCurrentWidget = nullptr;
    }
    if (widget != nullptr) {
        mCurrentWidget = widget;
        if (mCurrentWidget != nullptr) {
            mCurrentWidget->AddToViewport();
        }
    }
}

void AViewerGameModeBase::ChangeMenuWidget(WidgetSelector selector) {
    switch (selector) {
        case MAIN_MENU:
            changeWidget(mMainScreenWidget);
            break;
        case THREE_OPTION_MENU:
            changeWidget(mThreeOptionWidget);
            break;
        case TWO_OPTION_MENU:
            changeWidget(mTwoOptionWidget);
            break;
        case CONTENT_MENU:
            changeWidget(mContentScreenWidget);
            break;
    }
}

This compiles with no problem, but when I run it, I get an error:

PIE: Error: Abstract, Deprecated or Replaced classes are not allowed to be used to construct a user widget. MainScreenWidget is one of these.

All of my widgets inherit from UUserWidget like so:

class VIEWER_API UMainScreenWidget : public UUserWidget {

so I’m not sure what else I should do to remove this error?

UUserWidget is Abstract, you create a class of it in the Editor:

…This is useful for classes which are not meaningful on their own.

UCLASS(Abstract, ...)
class UMG_API UUserWidget : public UWidget, public INamedSlotInterface

So in your .h file you should declare:

UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSubclassOf<UMainScreenWidget> MainScreenWidgetClass;

UPROPERTY()
TObjectPtr<UMainScreenWidget> MainScreenWidget;

.cpp:

MainScreenWidget = CreateWidget<UMainScreenWidget>(GetWorld(), MainScreenWidgetClass);

Note the difference TSubclassOf and TObjectPtr.
In the Editor you reference the MainScreenWidgetClass using the Blueprint UserWidget inherited.

So, you are create a class of your UserWidget abstract class. ( which makes sense since UUserWidget is created using Blueprint, as a “template” )

1 Like

Oh, wow! Okay, that makes sense – since UUserWidget is abstract, inheriting it for my own widget creates my own custom, but still abstract, widget.

If my custom widget is still abstract, that should mean that there’s a number of virtual methods that haven’t been defined – looking at the documentation for UUserWidget that seems to be true.

As a matter of practicality, then, I imagine that most developers don’t create any pure C++ widgets, and are always using the blueprint system to instantiate them? It looks like there’s dozens of methods you’d need to populate by hand, and I imagine instatiating it in the blueprints system somehow defines these methods?

Okay, so in my .h file I updated it to read:

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UMainScreenWidget> MainScreenWidgetClass;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TObjectPtr<UMainScreenWidget> mMainScreenWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UThreeOptionWidget> ThreeOptionWidgetClass;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TObjectPtr<UThreeOptionWidget> mThreeOptionWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UTwoOptionWidget> TwoOptionWidgetClass;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TObjectPtr<UTwoOptionWidget> mTwoOptionWidget;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UContentScreenWidget> ContentScreenWidgetClass;
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TObjectPtr<UContentScreenWidget> mContentScreenWidget;

and then in my .cpp file, the BeginPlay() function now reads:

void AGutViewerGameModeBase::BeginPlay() {
    Super::BeginPlay();
    UGameUserSettings* gameSettings = GEngine->GetGameUserSettings();
    gameSettings->SetScreenResolution(FIntPoint(1440, 960));

    mMainScreenWidget = CreateWidget<UMainScreenWidget>(GetWorld(), MainScreenWidgetClass);
    mThreeOptionWidget = CreateWidget<UThreeOptionWidget>(GetWorld(), ThreeOptionWidgetClass);
    mTwoOptionWidget = CreateWidget<UTwoOptionWidget>(GetWorld(), TwoOptionWidgetClass);
    mContentScreenWidget = CreateWidget<UContentScreenWidget>(GetWorld(), ContentScreenWidgetClass);

    ChangeMenuWidget(WidgetSelector::MAIN_MENU);
}

But this now gives me errors:

PIE: Error: CreateWidget called with a null class.
PIE: Error: CreateWidget called with a null class.
PIE: Error: CreateWidget called with a null class.
PIE: Error: CreateWidget called with a null class.

I’m not sure how this is a null class? Did I do something wrong?

As a matter of practicality, then, I imagine that most developers don’t create any pure C++ widgets, and are always using the blueprint system to instantiate them? It looks like there’s dozens of methods you’d need to populate by hand, and I imagine instatiating it in the blueprints system somehow defines these methods?

This video explains better:
Making UIs With C++ in Unreal Engine, by Ben UI

Did you assign your created UserWidget Blueprints to your GameMode’s *WidgetClass variables in the Editor?

1 Like

Tip, if you want to avoid having a list of pointer classes and want to give the designer more control (if you work in a team) it is sometimes easier for everyone to not implement this in c++. You could still do something like:

//  UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional))
UPROPERTY(BlueprintReadOnly, meta = (BindWidget))
USomeWidget* SomeWidget = nullptr;

These bindings are automatically filled when a designer adds a widget to the UserWidget’s tree with the same name. A binding works if added on a c++ widget inheriting from the UUserWidget class.

Even better if there are no bindings at all and the designer can just drop in whatever. In most cases that gives the least amount of headaches while you can still write all functional logic 100% in c++ classes.

I’d also not expect any visual logic to be present in the GameMode, perhaps move it to the HUD? Reason that each class has its own small set of tasks. If you mix that up by trying to manage A B C D other tasks from a random class it spaghetifies.

2 Likes

Perhaps I’m misunderstanding what this code does, but I thought that’s what CreateWidget did? In BeginPlay() I call:

    mMainScreenWidget = CreateWidget<UMainScreenWidget>(GetWorld(), MainScreenWidgetClass);
    mThreeOptionWidget = CreateWidget<UThreeOptionWidget>(GetWorld(), ThreeOptionWidgetClass);
    mTwoOptionWidget = CreateWidget<UTwoOptionWidget>(GetWorld(), TwoOptionWidgetClass);
    mContentScreenWidget = CreateWidget<UContentScreenWidget>(GetWorld(), ContentScreenWidgetClass);

and I assumed that was assigning the created UserWidgets to the pointers.

You are right, but C++ needs to know what MainScreenWidgetClass Blueprint it will use to create an instance.
You can create multiples UUserWidget inherited Blueprint as well as UMainScreenWidget inherited Blueprint, which one do you want to use?

That’s why you need to assign the Blueprint you want to use in AViewerGameModeBase’s MainScreenWidgetClass property ( Edit AViewerGameModeBase Blueprint and look for MainScreenWidgetClass and others WidgetClass ).

1 Like

This is a very good point!

In my .h file I did as you recommended and set up the pointers with the BindWidget meta:

    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
    UMainScreenWidget* mMainScreenWidget;

And this makes sense to me that it creates a pointer, and allows me to define this pointer at a later point in Blueprints.

I created a GameMode blueprint that inherits from the header (BP_GameMode), and inside of this blueprint I went ahead and called Create Widget on each of my blueprint widgets:

This seems to give me the behavior that I want!

thank you!

thank you so much for taking the time to explain this – your explanations have been super helpful in helping me think through where my understanding was lacking. Super helpful!

If you add a property to a UserWidget named “ButtonWidget” (your own name) using BindWidget, you can simply add it to the widget hierarchy:

Saves you all those CreateWidget nodes.

Besides, if you do want to use the CreateWidget node be sure to pass on a valid owning player to those now empty pins. If you don’t, I believe it doesn’t get a valid player context. I ran into that while I wondered why in the world my event OnInitialized did not execute, well, it needs a valid player context.

That would work for adding a widget to another widget (as a sub-widget?) but I don’t think that works with GameModes, does it?

Would you recommend as a best practice to have a parent widget and then add sub-widgets this way?

Yes

BindWidget does not work in a gamemode. gamemode is not the place to implement UI. I’d only set the HUD class on the gamemode and let the HUD handle widget stuff.

I recommend implementing base functionality in c++ and keeping it separate from any UI design. This is easily done by creating an abstract c++ UserWidget containing those BindWidget(Optional) properties only if required for the base functionality to work correctly. Anything else, anything optional, should be up to the UI designer to do or not to do, by creating blueprint variants inheriting from the c++ class.

I thought I read somewhere that it was common to only have non-interactive widgets in the HUD (like healthbars) – is it common to actually place all of the UI elements there? Even pause menus, save menus, etc?

The HUD as in the AHUD Class is what I use to create UserWidgets, those UserWidgets are everything from health bars to menus. Every playercontroller can access its own HUD which makes it even more useful.