Implement a UMG health bar that updates without ticking every frame
Implement a UMG health-bar widget whose ProgressBar updates only when health actually changes — no Event Tick, no per-frame UMG property binding.
Constraints:
- The widget subscribes once to the health component's
OnHealthChangeddelegate. - The
ProgressBarpercent is set only inside the change handler. - Show the correct value once on bind, before the first event fires.
void UHealthBarWidget::BindToComponent(UHealthComponent* Comp)
{
// your code here
}
void UHealthBarWidget::HandleHealthChanged(float NewHealth, float MaxHealth)
{
// your code here
}
Write the implementation.
Drive the bar from a delegate: the health component broadcasts an OnHealthChanged event, and the widget updates its ProgressBar only in that handler. No Event Tick and no per-frame property binding are needed.
- ✗Using a UMG property binding, which silently re-evaluates every frame
- ✗Polling health in
NativeTickinstead of reacting to a change event - ✗Forgetting to set the bar to its current value once on construction
- →Why is a UMG property binding effectively a per-frame
Tick? - →How do you ensure the bar shows the correct value before the first event fires?
Idea
A UMG property binding looks cheap, but Slate re-evaluates it every frame — it is a hidden Tick. An event-driven model updates the bar only when health actually changes.
The health component declares a delegate:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnHealthChanged, float, NewHealth, float, MaxHealth);
UCLASS()
class UHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable) FOnHealthChanged OnHealthChanged;
void ApplyDamage(float Amount)
{
Health = FMath::Clamp(Health - Amount, 0.f, MaxHealth);
OnHealthChanged.Broadcast(Health, MaxHealth); // broadcast only on change
}
private:
float Health = 100.f;
float MaxHealth = 100.f;
};
The widget subscribes once and updates the ProgressBar only in the handler:
UCLASS()
class UHealthBarWidget : public UUserWidget
{
GENERATED_BODY()
UPROPERTY(meta = (BindWidget)) UProgressBar* HealthBar;
public:
void BindToComponent(UHealthComponent* Comp)
{
Comp->OnHealthChanged.AddDynamic(this, &UHealthBarWidget::HandleHealthChanged);
// set the starting value once, before the first event
HandleHealthChanged(Comp->GetHealth(), Comp->GetMaxHealth());
}
UFUNCTION()
void HandleHealthChanged(float NewHealth, float MaxHealth)
{
HealthBar->SetPercent(MaxHealth > 0.f ? NewHealth / MaxHealth : 0.f);
}
};
The widget's Event Tick is unused and there is no property binding — the bar redraws only when damage lands or healing occurs.