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 "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Dog.generated.h"
UCLASS()
class EXAMPLE_API ADog : public AActor
{
GENERATED_BODY()
public:
ADog( const FObjectInitializer& ObjectInitializer );
virtual void Tick(float DeltaTime) override;
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 BeginPlay() override;
UPROPERTY( VisibleAnywhere )
class UWidgetComponent* HealthWidgetComp;
FVector MovementVelocity;
float Health;
float MaxHealth = 120;
float HealthTweenDirection;
};
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 "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Dog.h"
#include "HealthBar.generated.h"
UCLASS( Abstract )
class EXAMPLE_API UHealthBar : public UUserWidget
{
GENERATED_BODY()
public:
void SetOwnerDog( ADog* InDog ) { OwnerDog = InDog; }
protected:
void NativeTick( const FGeometry& MyGeometry, float InDeltaTime ) override;
TWeakObjectPtr<ADog> OwnerDog;
UPROPERTY( meta = ( BindWidget ) )
class UProgressBar* HealthBar;
UPROPERTY( meta = ( BindWidget ) )
class UTextBlock* CurrentHealthLabel;
UPROPERTY( meta = ( BindWidget ) )
class 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 ) );
}