Files
ultimate-bar-system-plugin/Source/UniversalBars/Private/UniversalHUDWidget.cpp
Bonchellon 41a0cdef36 UniversalBars plugin: PEAK-style universal UMG bars
Procedural M_UI_UniversalBar material (SDF rounded-rect, fill/delayed/shield/
penalty-zones/stripes/segments/flash/pulse), 6 preset instances, animated
UUniversalBarWidget + UUniversalHUDWidget + global UUniversalBarsSubsystem API,
UUniversalBarsTheme DataAsset, and a ready WBP_HUD_InGame with zone-tracking
status chips. See README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:04:50 +03:00

218 lines
5.7 KiB
C++

// Copyright IHY. Universal Bars plugin.
#include "UniversalHUDWidget.h"
#include "UniversalBarWidget.h"
#include "UniversalBarsSubsystem.h"
#include "UniversalBarsTheme.h"
#include "Components/Image.h"
#include "Components/PanelWidget.h"
#include "Components/CanvasPanel.h"
#include "Components/CanvasPanelSlot.h"
void UUniversalHUDWidget::NativeConstruct()
{
Super::NativeConstruct();
ApplyTheme();
if (UUniversalBarsSubsystem* Sub = UUniversalBarsSubsystem::Get(this))
{
Sub->RegisterHUD(this);
}
}
void UUniversalHUDWidget::ApplyTheme()
{
if (!Theme)
{
return;
}
HungerColor = Theme->Hunger;
WeightColor = Theme->Weight;
ColdColor = Theme->Cold;
auto StyleBar = [this](UUniversalBarWidget* Bar, FLinearColor Fill)
{
if (!Bar)
{
return;
}
Bar->SetBarColors(Fill, Theme->Empty, Theme->Border, Theme->Delayed);
Bar->SetVector(TEXT("ShieldColor"), Theme->Shield);
Bar->DamageFlashColor = Theme->DamageFlash;
Bar->HealFlashColor = Theme->HealFlash;
Bar->SetScalar(TEXT("CornerRadius"), Theme->CornerRadius);
Bar->SetScalar(TEXT("BorderSize"), Theme->BorderSize);
Bar->SetScalar(TEXT("InnerPadding"), Theme->InnerPadding);
};
StyleBar(HealthBar, Theme->HealthFill);
StyleBar(StaminaBar, Theme->StaminaFill);
if (ChipsPanel && Theme->ChipColors.Num() > 0)
{
const int32 N = ChipsPanel->GetChildrenCount();
for (int32 i = 0; i < N; ++i)
{
if (UImage* Img = Cast<UImage>(ChipsPanel->GetChildAt(i)))
{
Img->SetBrushTintColor(FSlateColor(Theme->ChipColors[i % Theme->ChipColors.Num()]));
}
}
}
// Tracking chips: tint by what they represent.
if (ChipHeart) { ChipHeart->SetBrushTintColor(FSlateColor(Theme->HealthFill)); }
if (ChipHunger) { ChipHunger->SetBrushTintColor(FSlateColor(Theme->Hunger)); }
if (ChipWeight) { ChipWeight->SetBrushTintColor(FSlateColor(Theme->Weight)); }
if (ChipCold) { ChipCold->SetBrushTintColor(FSlateColor(Theme->Cold)); }
}
void UUniversalHUDWidget::NativeDestruct()
{
if (UUniversalBarsSubsystem* Sub = UUniversalBarsSubsystem::Get(this))
{
Sub->UnregisterHUD(this);
}
Super::NativeDestruct();
}
void UUniversalHUDWidget::SetHealth(float Percent)
{
if (HealthBar) { HealthBar->SetFillPercent(Percent, true); }
}
void UUniversalHUDWidget::SetStamina(float Percent)
{
if (StaminaBar) { StaminaBar->SetFillPercent(Percent, true); }
}
void UUniversalHUDWidget::SetShield(float Percent)
{
if (HealthBar) { HealthBar->SetShieldPercent(Percent); }
}
void UUniversalHUDWidget::SetHunger(float Amount)
{
if (HealthBar) { HealthBar->SetPenaltyZone(TEXT("Hunger"), Amount, HungerColor); }
}
void UUniversalHUDWidget::SetWeight(float Amount)
{
if (HealthBar) { HealthBar->SetPenaltyZone(TEXT("Weight"), Amount, WeightColor); }
}
void UUniversalHUDWidget::SetCold(float Amount)
{
if (HealthBar) { HealthBar->SetPenaltyZone(TEXT("Cold"), Amount, ColdColor); }
}
void UUniversalHUDWidget::DamageHealth(float Delta)
{
if (HealthBar) { HealthBar->SetFillPercent(HealthBar->GetFillPercent() - FMath::Abs(Delta), true); }
}
void UUniversalHUDWidget::HealHealth(float Delta)
{
if (HealthBar) { HealthBar->SetFillPercent(HealthBar->GetFillPercent() + FMath::Abs(Delta), true); }
}
float UUniversalHUDWidget::GetHealth() const
{
return HealthBar ? HealthBar->GetFillPercent() : 0.f;
}
void UUniversalHUDWidget::SetDemoMode(bool bEnable)
{
bDemoMode = bEnable;
bDemoInit = false;
DemoTimer = 0.f;
DemoStep = 0;
}
void UUniversalHUDWidget::DemoStepNow()
{
// Each step shows a different animation; between steps the bars ease on their own.
switch (DemoStep % 10)
{
case 0: DamageHealth(0.25f); break; // red flash + lag tail
case 1: SetStamina(0.30f); break; // stamina drop
case 2: HealHealth(0.20f); break; // green flash + smooth rise
case 3: SetHunger(0.18f); break; // hunger zone grows in
case 4: SetWeight(0.14f); break; // weight zone grows in
case 5: SetStamina(0.95f); break; // stamina refill
case 6: HealHealth(0.30f); break;
case 7: SetHunger(0.0f); break; // hunger zone retracts
case 8: SetWeight(0.0f); break; // weight zone retracts
case 9: DamageHealth(0.45f); break; // big hit -> low-HP pulse
}
++DemoStep;
}
void UUniversalHUDWidget::PositionChips()
{
if (!HealthBar)
{
return;
}
// Width of the bar in local pixels — ChipCanvas is overlaid on HealthBar so
// the same width maps zone-U (0..1) straight to chip X.
const float W = HealthBar->GetCachedGeometry().GetLocalSize().X;
if (W <= 1.f)
{
return; // not laid out yet this frame
}
auto Place = [W](UImage* Chip, float U)
{
if (!Chip)
{
return;
}
if (U < 0.f)
{
Chip->SetVisibility(ESlateVisibility::Collapsed); // zone absent
return;
}
Chip->SetVisibility(ESlateVisibility::HitTestInvisible);
if (UCanvasPanelSlot* CSlot = Cast<UCanvasPanelSlot>(Chip->Slot))
{
const float ChipW = CSlot->GetSize().X;
// centre over the zone, sitting just above the bar (negative Y)
CSlot->SetPosition(FVector2D(U * W - ChipW * 0.5f, -ChipW - 6.f));
}
};
Place(ChipHeart, HealthBar->GetUsableEnd());
Place(ChipHunger, HealthBar->GetZoneCenter(TEXT("Hunger")));
Place(ChipWeight, HealthBar->GetZoneCenter(TEXT("Weight")));
Place(ChipCold, HealthBar->GetZoneCenter(TEXT("Cold")));
}
void UUniversalHUDWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
PositionChips(); // always track the bar, demo or not
if (!bDemoMode)
{
return;
}
if (!bDemoInit)
{
bDemoInit = true;
SetHealth(1.f);
SetStamina(1.f);
SetHunger(0.f);
SetWeight(0.f);
DemoTimer = 0.f;
DemoStep = 0;
}
DemoTimer += InDeltaTime;
if (DemoTimer >= DemoStepInterval)
{
DemoTimer = 0.f;
DemoStepNow();
}
}