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>
This commit is contained in:
217
Source/UniversalBars/Private/UniversalHUDWidget.cpp
Normal file
217
Source/UniversalBars/Private/UniversalHUDWidget.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user