In this tutorial video, I explain how to set up in-world widgets that are attached to actors, using the UWidgetComponent
class.
Dog.h
#pragma once
#include "GameFramework/Actor.h"
#include "Dog.generated.h"
class UWidgetComponent;
UCLASS()
class ADog : public AActor
{
GENERATED_BODY()
public:
ADog(const FObjectInitializer& ObjectInitializer);
float GetHealth() const { return Health; }
void SetHealth(float Val) { Health = Val; }
float GetMaxHealth() const { return MaxHealth; }
void SetMaxHealth(float Val) { MaxHealth = Val; }
protected:
virtual void Tick(float DeltaTime) override;
virtual void BeginPlay() override;
UPROPERTY(VisibleAnywhere)
TObjectPtr<UWidgetComponent> HealthWidgetComp;
FVector MovementVelocity = FVector::ZeroVector;
float Health = 0;
float MaxHealth = 120;
float HealthTweenDirection = 0;
};
Dog.cpp
#include "Dog.h"
#include "Components/WidgetComponent.h"
#include "HealthBar.h"
ADog::ADog(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryActorTick.bCanEverTick = true;
if (RootComponent == nullptr)
{
RootComponent = ObjectInitializer.CreateDefaultSubobject<USceneComponent>(this, TEXT("Root"));
}
HealthWidgetComp = ObjectInitializer.CreateDefaultSubobject<UWidgetComponent>(this, TEXT("HealthBar"));
HealthWidgetComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);
Health = MaxHealth;
}
void ADog::BeginPlay()
{
Super::BeginPlay();
UHealthBar* HealthBar = Cast<UHealthBar>(HealthWidgetComp->GetUserWidgetObject());
HealthBar->SetOwnerDog(this);
// Make sure every dog starts with different health
Health = FMath::RandRange(0.0f, MaxHealth);
HealthTweenDirection = FMath::RandBool() ? 1 : -1;
// Move in a random direction at a random speed
const float DirRads = FMath::RandRange(0.0f, 2 * PI);
MovementVelocity = FVector(FMath::Cos(DirRads), FMath::Sin(DirRads), 0) * 10.0f;
// Face that way too
RootComponent->SetWorldRotation(FRotator::MakeFromEuler(FVector(0, 0, DirRads / PI * 180 - 90)));
}
void ADog::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
SetActorLocation(GetActorLocation() + MovementVelocity * DeltaTime);
// Bounce health between min and max to show off health bar
static const float TweenSpeed = 10.0f;
Health = FMath::Clamp<float>(Health + DeltaTime * HealthTweenDirection * TweenSpeed, 0, MaxHealth);
if ((Health == MaxHealth && HealthTweenDirection == 1)
|| (Health == 0 && HealthTweenDirection == -1))
{
HealthTweenDirection *= -1;
}
}
HealthBar.h
#pragma once
#include "Blueprint/UserWidget.h"
#include "Dog.h"
#include "HealthBar.generated.h"
class UProgressBar;
class UTextBlock;
UCLASS(Abstract)
class UHealthBar : public UUserWidget
{
GENERATED_BODY()
public:
void SetOwnerDog(ADog* InDog) { OwnerDog = InDog; }
protected:
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
TWeakObjectPtr<ADog> OwnerDog;
UPROPERTY(meta=(BindWidget))
TObjectPtr<UProgressBar> HealthBar;
UPROPERTY(meta=(BindWidget))
TObjectPtr<UTextBlock> CurrentHealthLabel;
UPROPERTY(meta=(BindWidget))
TObjectPtr<UTextBlock> MaxHealthLabel;
};
HealthBar.cpp
#include "HealthBar.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
void UHealthBar::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
if (!OwnerDog.IsValid())
return;
HealthBar->SetPercent(OwnerDog->GetHealth() / OwnerDog->GetMaxHealth());
FNumberFormattingOptions Opts;
Opts.SetMaximumFractionalDigits(0);
CurrentHealthLabel->SetText(FText::AsNumber(OwnerDog->GetHealth(), &Opts));
MaxHealthLabel->SetText(FText::AsNumber(OwnerDog->GetMaxHealth(), &Opts));
}