How do i use a UWidgetComponent ?

Hello everyone,

I’m trying to wrap my (fairly complex) slate UI in a widget component attached to the player’s actor.
So i wrote a UUserWidget derived class, very simple, here it is in its full glory:



.h:
UCLASS()
class YAG_API UYag3DWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	UYag3DWidget(const FObjectInitializer& ObjectInitializer);
	void SetContent(TSharedRef<SWidget> InContent);
	bool CheckMyWidget();
protected:
	virtual TSharedRef<SWidget> RebuildWidget() override;
};

---------------------------------------------------------------------------
.cpp:
UYag3DWidget::UYag3DWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{}

void UYag3DWidget::SetContent(TSharedRef<SWidget> InContent) { MyWidget = InContent; }

bool UYag3DWidget::CheckMyWidget() { return MyWidget.IsValid(); }

TSharedRef<SWidget> UYag3DWidget::RebuildWidget() { return MyWidget.Pin().ToSharedRef(); }


In the actor’s class constructor i declared a UWidgetComponent and an instance of this 3D widget as follow:



This3DWidget = ObjectInitializer.CreateDefaultSubobject<UYag3DWidget>(this, TEXT("3D widget"));

ThisWidgetComponent = ObjectInitializer.CreateDefaultSubobject<UWidgetComponent>(this, TEXT("widget component"));


All this compiles well, but as soon as i try to set the WidgetComponent’s widget, i get a crash:



This3DWidget->SetContent(YagHUD->YagHUDWidget.ToSharedRef());

if (This3DWidget->CheckMyWidget())
{
	ThisWidgetComponent->SetWidgetClass(This3DWidget->GetClass());
	ThisWidgetComponent->SetWidget(This3DWidget);
}


Where YagHUDWidget is the SCompoundWidget that contains all my UI.

As soon as the SetWidget function is called (no matter when), i get a crash when UUserWidget::OnWidgetRebuilt() is called because WidgetTree is empty:

I posted a question on the answerhub, but as it’s not a bug report and given that 4.10 has just been released, i suspect it won’t get much attention:
https://answers.unrealengine.com/questions/331845/how-to-use-uwidgetcomponentsetwidget.html

So i was wondering if anyone succeeded in using a UWidgetComponent and could help me in my holy quest for a full 3D UI :slight_smile:

Sorry for the long post, just the result of a long struggle.

Cheers

Cedric

1 Like

Hey All,

Anyone bumping into this problem, here’s a solution.
As always, it’s probably not be the best but it works for me, i hope it’ll help.

YagHUD->YagHUDWidget is the SCompoundWidget containing the whole UI.

It is initialized in my hud class the usual way:


 .h

	TSharedPtr<class SYagHUDWidget> YagHUDWidget;

----------------------------------------------------------------------------------------------

.cpp

void AYagHUD::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	
	if (GEngine && GEngine->GameViewport)
	{
		// get viewport
		UGameViewportClient* Viewport = GEngine->GameViewport;

		// mother widget that contains everything
		SAssignNew(YagHUDWidget, SYagHUDWidget)
			.YagHUD(TWeakObjectPtr<AYagHUD>(this));
		
		// add the mother widget
		// uncomment to use as a regular 2D UI, comment when used in a 3D widget component
		//Viewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(YagHUDWidget.ToSharedRef()));
		
		// workaround for the selection problem
		YagHUDWidget->SetVisibility(EVisibility::SelfHitTestInvisible);
	}
}

But instead of adding it to the viewport in the hud class (commented line), i use it in a UUserWidget:


.h

UCLASS()
class YAG_API UYag3DWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	UYag3DWidget(const FObjectInitializer& ObjectInitializer);
protected:
	virtual TSharedRef<SWidget> RebuildWidget() override;
};

----------------------------------------------------------------------------------------------

.cpp

UYag3DWidget::UYag3DWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{	
}

TSharedRef<SWidget> UYag3DWidget::RebuildWidget()
{
	// get PC (needed to avoid a crash when opening the blueprint editor)
	APlayerController* ThisPC = Cast<APlayerController>(GetWorld()->GetFirstPlayerController());
	if (!ThisPC) return SNew(SYagHelpWidget);

	AYagHUD* YagHUD = Cast<AYagHUD>(ThisPC->GetHUD());
	if (!YagHUD) return SNew(SYagHelpWidget);

	return YagHUD->YagHUDWidget.ToSharedRef();
}

This way i can give my users a choice between 2D (hud) and 3D (widget) UI.

My initial goal was to wrap exactly my slate UI as it is defined in the hud, but if you don’t need a hud, i suppose the RebuildWidget could be even simpler, instead of returning the hud widget, just instanciate it directly in the user widget, something like that:


TSharedRef<SWidget> UYag3DWidget::RebuildWidget()
{
	return SNew(SYagHUDWidget);
}

I didn’t test this simpler version as my code contains a lot of calls to YagHUD->YagHUDWidget and would break if this widget wasn’t instanciated.

Anyhow, here is how i use it in my actor’s class:


.h

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = YagUI)
	UWidgetComponent* ThisWidgetComponent;
--------------------------------------------------------------------------------------------

.cpp (inside constructor)

ThisWidgetComponent = ObjectInitializer.CreateDefaultSubobject<UWidgetComponent>(this, TEXT("widget component"));
ThisWidgetComponent->AttachTo(RootComponent);
ThisWidgetComponent->SetWidgetClass(UYag3DWidget::StaticClass());
ThisWidgetComponent->SetOnlyOwnerSee(true);
ThisWidgetComponent->SetIsReplicated(false);
ThisWidgetComponent->SetRelativeRotation(FRotator(0.f, 180.f, 0.f));
ThisWidgetComponent->SetDrawSize(FVector2D(1920, 1080));
ThisWidgetComponent->SetRelativeScale3D(FVector(1.f, 1.f, 1.f));
ThisWidgetComponent->SetRelativeLocation(CameraComponentDefaultRelativeLocation + FVector(1000.f, 0.f, -HumanHeight + 300.f));
// collisions
ThisWidgetComponent->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
ThisWidgetComponent->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
ThisWidgetComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
// block camera & visibility for mouse cursor
ThisWidgetComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Block);
ThisWidgetComponent->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);

The only problem left for me is that the widget works very well on server but is not responsive to mouse cursor on clients (despite the collisions being set properly).

I’ll post the solution to this remaining problem when i understand it.

Cheers

Cedric

Hey Cedric, did you ever figure out the client problem? I saw in one of your Answer Hub posts that it only affected play-in-editor clients and worked in packaged games. Is that right?

Hey Muchcharles,

im afraid i won’t be of great use on this one, that’s an old problem i didn’t even remember it before reading your question :slight_smile:

If memory serves, i actually had a few problems that occured only in PIE, it’s very possible that this one was on the list. It sort of rings a bell.

Anyway, since then the input management has changed (the widget interaction comes into mind) so whatever i could tell you on this issue would probably be outdated.

If i could talk to the one-year-ago cedric, i’d probably advise him to also have a look at the InputComponent :slight_smile:

Fortunately, once you have your widget working, testing the input shouldn’t be too hard.

Sorry for the lack of a better answer and best luck :slight_smile:

Cheers

Cedric