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>
218 lines
5.7 KiB
C++
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();
|
|
}
|
|
}
|