If you want static HEalth bar, lets say on top/bottom of your screen then you can do it in Slate but if you want to have Multiple health bars on your screen for multiple/dynamic actors, it would be much easier to make it using HUD and Deproject function.
Anyway, here is really simple dynamic health bar implementation in Slate you can start with:
So all you have to do is create “SHealthBar.h” in your project and paste this into it:
#pragma once
#include "Slate.h"
DECLARE_DELEGATE_RetVal(float, FOnGetFloat)
class SHealthBar : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SHealthBar)
{}
SLATE_ARGUMENT(float, MaxHealth)
SLATE_EVENT(FOnGetFloat, OnGetCurrentHealth)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
MaxHealth = InArgs._MaxHealth;
OnGetCurrentHealth = InArgs._OnGetCurrentHealth;
}
virtual int32 OnPaint(const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const OVERRIDE
{
// Initialize our brush here
const FSlateBrush* BrushResource = new FSlateBrush();
// Draw background box
FSlateDrawElement::MakeBox(
OutDrawElements,
LayerId,
AllottedGeometry.ToPaintGeometry(),
BrushResource,
MyClippingRect,
ESlateDrawEffect::None,
FLinearColor::Gray *.35f
);
if (OnGetCurrentHealth.IsBound())
{
// Calculate CurrentHealth / MaxHealth ratio and turn it into screen width
FVector2D WidgetSize = MyClippingRect.GetSize();
float HealthWidth = ( OnGetCurrentHealth.Execute() * WidgetSize.X ) / MaxHealth;
// Draw current health
FSlateDrawElement::MakeBox(
OutDrawElements,
LayerId,
AllottedGeometry.ToPaintGeometry(FVector2D::ZeroVector, FVector2D(HealthWidth, WidgetSize.Y) ),
BrushResource,
MyClippingRect,
ESlateDrawEffect::None,
FLinearColor::Green *.65f
);
// Draw text on health bar
FSlateFontInfo MyFont(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Regular.ttf"), 15);
const FText Text = GetHealthText();
const TSharedRef< FSlateFontMeasure > FontMeasureService = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
FVector2D DrawSize = FontMeasureService->Measure(Text, MyFont);
FVector2D Pos = WidgetSize / 2.0f - DrawSize / 2.0f;
FSlateDrawElement::MakeText(
OutDrawElements,
LayerId,
AllottedGeometry.ToPaintGeometry(Pos, DrawSize),
Text,
MyFont,
MyClippingRect,
ESlateDrawEffect::None,
FLinearColor::White
);
}
return SCompoundWidget::OnPaint(AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
}
protected:
FText GetHealthText() const
{
if (OnGetCurrentHealth.IsBound())
{
return FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), OnGetCurrentHealth.Execute(), MaxHealth));
}
return FText();
}
float MaxHealth;
FOnGetFloat OnGetCurrentHealth;
};
after that you should be ready to go. To use it you need to pass 2 arguments: MaxHealth and Delegate that will return current health value. I did it in my TestCharacter class like that
GEngine->GameViewport->AddViewportWidgetContent(
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Top)
SNew(SBox)
.WidthOverride(500)
.HeightOverride(40)
SNew(SHealthBar)
.MaxHealth(MaxHealth)
.OnGetCurrentHealth(FOnGetFloat::CreateUObject(this, &ATestCharacter::GetHealth))
]
]
);
where ATestCharacter::GetHealth looks like this:
float ATestCharacter::GetHealth() const { return Health; }
this is how it should look like
Hope this helps