RootWidget is nullptr when trying to add Widgets to my UI (4.20.3)

Hey all, to describe the entire hierarchy:

  1. I have a C++ GameMode which sets the DefaultPawnClass and PlayerControllerClass.

  2. I have a C++ PlayerController which owns a member: UInGameUI which is the UserWidget I’ve created. In BeginPlay I create my UserWidget:



void AFPPlayerController::BeginPlay()
{
    Super::BeginPlay();

    inGameUI = CreateWidget<UInGameUI>(this, UInGameUI::StaticClass());
    FInputModeGameAndUI mode;
    mode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
    mode.SetHideCursorDuringCapture(false);
    SetInputMode(mode);
    inGameUI->AddToViewport();
}


  1. The C++ UserWidget ( InGameUI ) overrides NativeConstruct and looks like this in the overriden NativeConstruct:


void UInGameUI::NativeConstruct()
{
    Super::NativeConstruct();

    UPanelWidget* rootWidget = Cast<UPanelWidget>(GetRootWidget());

    // Don't need to check if WidgetTree is nullptr since RootWidget is a member of WidgetTree.
    if (!rootWidget)
    {
        return;
    }

    // Setup inventory icon
    {
        inventoryIcon = WidgetTree->ConstructWidget<UImage>(UImage::StaticClass(), TEXT("Inventory Icon"));
        rootWidget->AddChild(inventoryIcon);

        UCanvasPanelSlot* inventoryIconSlot = CastChecked<UCanvasPanelSlot>(inventoryIcon->Slot);
        FAnchors inventoryAnchor(1.0f, 1.0f);
        FVector2D inventorySize(100.0f, 100.0f);
        FVector2D inventoryPosition = -inventorySize * 1.5f;

        inventoryIcon->SetVisibility(ESlateVisibility::Hidden);
        inventoryIconSlot->SetAnchors(inventoryAnchor);
        inventoryIconSlot->SetPosition(inventoryPosition);
        inventoryIconSlot->SetSize(inventorySize);
    }

    // Setup crosshair
    {
        crosshair = WidgetTree->ConstructWidget<UImage>(UImage::StaticClass(), TEXT("Crosshair"));
        crosshair->SetBrush(crosshairBrush);
        rootWidget->AddChild(inventoryIcon);

        UCanvasPanelSlot* crosshairSlot = CastChecked<UCanvasPanelSlot>(crosshair->Slot);
        FAnchors crosshairAnchor(0.5f, 0.5f);
        FVector2D crosshairSize(65.0f, 65.0f);
        FVector2D crosshairPosition(0.0f, 0.0f);

        crosshairSlot->SetAnchors(crosshairAnchor);
        crosshairSlot->SetPosition(crosshairPosition);
        crosshairSlot->SetSize(crosshairSize);
    }
}


.h file:



UCLASS()
class REMNANT_API UInGameUI : public UUserWidget
{
    GENERATED_BODY()

public:

    virtual void NativeConstruct() override;

    void SetInventoryIcon(FSlateBrush& brush);
    void SetCrosshairBrush(FSlateBrush& brush);
    void SetCrosshairOpacity(float opacity);

    void ResetInventoryIcon();

private:

    UPROPERTY()
    UImage* inventoryIcon;

    UPROPERTY()
    UImage* crosshair;

    UPROPERTY(EditAnywhere)
    FSlateBrush crosshairBrush;
};


  1. I’ve created a UMG Blueprint ( UserInterface → Widget ) in the Content tab, and set it’s parent class to my own InGameUI so the exposed crosshairBrush variable is available in the graph, I’ve also set it to the appropriate asset.

My issue, in the UserWidgets overriden NativeConstruct, the RootWidget returns as a nullptr, so the editor crashes when trying to start the game, and I have no idea why it’s nullptr.

Also, a small bonus question, I’m trying to alter the brush of my InGameUI’s member inventoryIcon at runtime when I interact with pickups, so I’ve cached the InGameUI in a member variable in my ActorComponent InteractComponent’s BeginPlay like so:



cachedInGameUI = Cast<AFPPlayerController>(GetWorld()->GetFirstPlayerController())->GetInGameUI();


However, my Levels actor’s gets their BeginPlay called before the PlayerControllers’ BeginPlay, so the UI has not yet been created and the cached pointer is nullptr. It feels very weird that they would execute before PlayerController since it can be used to cache objects and save some overhead, am I missing something?

Appreciate any answers, thanks in advance.

Don’t rely on BeginPlay() to create objects, you can’t control or predict when any will be called…
Subscribe to delegates instead.