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:
Bonchellon
2026-06-23 19:04:50 +03:00
commit 41a0cdef36
24 changed files with 1262 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# UE build artifacts
Intermediate/
Binaries/
DerivedDataCache/
Saved/
*.sln
*.suo
*.VC.db
.vs/

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

231
README.md Normal file
View File

@ -0,0 +1,231 @@
# Universal Bars
PEAK-style universal UMG bars for Unreal Engine 5.7 — one procedural material
drives every kind of stat bar (health, stamina, hunger, thirst, oxygen, cold,
shield, injury, boss HP, item durability, progress), with a global subsystem
API and animated damage / heal / low-value feedback.
Everything is **procedural** — no bar textures required. White rounded border,
soft corners, dark translucent backdrop, bright flat fill: clean cartoon
survival HUD.
---
## 1. Install / enable
The plugin lives at `Plugins/UniversalBars/`. It is a project plugin and is
enabled by default. After pulling, regenerate project files and build the
editor target once (it adds the `UniversalBars` runtime module). Content mounts
under `/UniversalBars/`.
> New C++ classes (a new module) cannot be hot-loaded by Live Coding — do a full
> editor build the first time.
---
## 2. What's inside
```
/UniversalBars/
Materials/
M_UI_UniversalBar master material (Domain=UI, Translucent, procedural SDF)
MI_UI_Bar_Health solo green bar
MI_UI_Bar_Stamina solo yellow-green bar
MI_UI_Bar_Injury striped "broken HP" bar
MI_UI_Bar_Shield blue bar + shield overlay
MI_UI_Bar_Boss segmented boss bar
MI_UI_Bar_PeakCombined HP + hunger + weight in ONE bar (PEAK capacity)
Components/
WBP_UniversalBar one reusable bar (parent: UUniversalBarWidget)
WBP_BarHealth combined-capacity variant
WBP_BarStamina stamina variant
WBP_HUD_InGame ready HUD: health + stamina + status chips
Theme/ put DA_UniversalBars_* theme assets here
C++ (module UniversalBars):
UUniversalBarWidget one animated bar
UUniversalHUDWidget HUD container with high-level stat setters
UUniversalBarsSubsystem global access point (UWorldSubsystem)
UUniversalBarsTheme palette / geometry / chip-colour DataAsset
```
---
## 3. Quick start
### Blueprint — easiest path (global API, no widget reference)
1. On `BeginPlay` (player controller / HUD), call
`Universal Bars Subsystem → Create HUD` with `HUD Class = WBP_HUD_InGame`.
2. From **anywhere** in BP:
```
Get World Subsystem (UniversalBarsSubsystem)
→ Set Health (0.62) // 0..1, animates + flashes automatically
→ Set Stamina (0.8)
→ Set Hunger (0.16) // penalty zone eats into max HP (combined bar)
→ Set Weight (0.12)
→ Damage Health (0.2) // red flash + instant drop + lag tail
→ Heal Health (0.25) // green flash + smooth rise
```
Direction is detected for you: a decrease plays the damage animation, an
increase plays the heal animation.
### C++
```cpp
#include "UniversalBarsSubsystem.h"
if (UUniversalBarsSubsystem* Bars = UUniversalBarsSubsystem::Get(this))
{
Bars->SetHealth(0.62f);
Bars->SetHunger(0.16f);
Bars->DamageHealth(0.2f);
}
```
### Driving a single bar directly
Place a `WBP_UniversalBar` (or a preset variant) anywhere and call:
```cpp
UUniversalBarWidget* Bar = ...;
Bar->SetFillPercent(0.5f); // animated
Bar->SetShieldPercent(0.25f);
Bar->SetSegmentCount(10); // boss segments
Bar->SetPenaltyZone("Hunger", 0.16f, FLinearColor(0.9f,0.6f,0.2f,1)); // animated zone
Bar->PlayDamageFlash(); // manual flash
```
---
## 4. Animation model (same as the HTML prototype)
| Change | Bright fill | Lag tail | Flash |
|---|---|---|---|
| **Decrease / damage** | snaps down instantly | holds `DelayedDelay`s, then drains at `DelayedFallSpeed` | red (`DamageFlashColor`) |
| **Increase / heal** | eases up at `FillRiseSpeed` | jumps to target (lighter "incoming" band) | green (`HealFlashColor`) |
| **value < `LowThreshold`** | — | — | border pulses (low-value warning) |
| **penalty zone change** | — | zone width eases at `ZoneInterpSpeed` | — |
All tuning fields live on `UUniversalBarWidget` (Bar|Anim category). Set
`bAutoFlashOnChange=false` to drive flashes manually.
Under the hood the material exposes `FillPercent`, `DelayedFillPercent`,
`FlashAmount`, `LowHPPulseAmount` — the widget tweens these every tick via a
Dynamic Material Instance.
---
## 5. Combined "capacity" bar (PEAK)
One bar = current value (bright) + depleted-but-recoverable zone (dark) +
striped penalty zones eating max from the right:
- `MaxFillPercent` — right edge of the usable region (`1 - sum(penalties)`).
- `PenaltyCount` + `PenaltyEnd1..4` + `PenaltyColor1..4` — up to 4 striped zones.
You never set these by hand — `SetHunger / SetWeight / SetCold` (HUD) or
`SetPenaltyZone(Id, Width, Color)` (bar) manage them and animate the widths.
Set a penalty to `0` to retract its zone.
`PenaltyCount = 0` → the same material is a plain solo bar.
---
## 6. Material parameter reference
**Scalars:** `FillPercent`, `DelayedFillPercent`, `ShieldPercent`,
`MaxFillPercent`, `PenaltyCount`, `PenaltyEnd1..4`, `BarOpacity`,
`BackgroundOpacity`, `CornerRadius`, `BorderSize`, `InnerPadding`,
`EdgeSoftness`, `BarAspect`, `FlashAmount`, `LowHPPulseAmount`, `PulseSpeed`,
`StripeAmount`, `StripeScale`, `StripeSpeed`, `SegmentCount`,
`SegmentDividerSize`, `SegmentDividerOpacity`, `UseVerticalFill`, `InvertFill`,
`NoiseAmount`, `TimeOffset`.
**Vectors:** `FillColor`, `DelayedFillColor`, `ShieldColor`, `BackgroundColor`,
`BorderColor`, `EmptyColor`, `FlashColor`, `StripeColorA`, `StripeColorB`,
`PenaltyColor1..4`.
`BarAspect` must roughly match the on-screen width/height ratio so the rounded
corners stay circular (default 8 ≈ a 600×75 bar).
---
## 7. Theme
`UUniversalBarsTheme` (DataAsset) centralises palette + geometry + chip colours.
Create one under `/UniversalBars/Theme/` (right-click → Miscellaneous → Data
Asset → Universal Bars Theme), then assign it to `WBP_HUD_InGame`'s `Theme`
property. `UUniversalHUDWidget::ApplyTheme()` runs on construct and retints both
bars and all chips. Per-preset look still lives in the `MI_UI_Bar_*` instances;
the theme is the HUD-level override.
---
## 8. Presets (recommended values)
| Preset | Fill | Notes |
|---|---|---|
| Health | `(0.30,0.85,0.20)` | white border, low-HP pulse, damage lag |
| Stamina | `(0.85,0.95,0.30)` | thinner, no pulse |
| Injury | `(0.75,0.20,0.20)` + `StripeAmount=1` | striped "broken" HP |
| Shield | `(0.30,0.70,1.0)` + `ShieldPercent` | blue + overlay |
| Boss | `(0.85,0.15,0.15)` + `SegmentCount=10` | wide, segmented, less rounded |
| PeakCombined | green + Hunger/Weight penalties | one-bar HP+hunger+weight |
Keep `BorderColor` pure white and `BorderSize` 0.060.10 for the toy/cartoon
look; `CornerRadius` 0.50.7 for horizontal bars.
---
## 9. Demo / study mode (WBP_HUD_InGame)
`WBP_HUD_InGame` ships with **Demo Mode ON** (`UUniversalHUDWidget::bDemoMode`).
While on, the HUD's `NativeTick` runs a 10-step cycle every `DemoStepInterval`
(1.6 s) so you can watch every animation live in PIE:
```
0 DamageHealth(0.25) red flash + instant drop + lag tail
1 SetStamina(0.30) stamina drop
2 HealHealth(0.20) green flash + smooth rise
3 SetHunger(0.18) amber zone eases in from the right
4 SetWeight(0.14) red zone eases in
5 SetStamina(0.95) stamina refill
6 HealHealth(0.30)
7 SetHunger(0.0) hunger zone retracts
8 SetWeight(0.0) weight zone retracts
9 DamageHealth(0.45) big hit -> low-HP border pulse
```
To watch: add the HUD (`Subsystem → Create HUD`, or just drop it in any widget /
PIE) and press Play. The cycle restarts each time the widget constructs.
**Turn it off for shipping:** open `WBP_HUD_InGame` → Class Defaults → uncheck
**Demo Mode**, or call `SetDemoMode(false)`. Read `DemoStepNow()` in
`UniversalHUDWidget.cpp` to see exactly which API call drives each animation.
## 9b. Status chips (PEAK markers)
In `WBP_HUD_InGame` the chips live in a `CanvasPanel` (`ChipCanvas`) **overlaid on
the health bar**, not in a row below it. Each tick `UUniversalHUDWidget::PositionChips()`
slides them to sit just above the centre of their zone, using
`UUniversalBarWidget::GetUsableEnd()` / `GetZoneCenter("Hunger"|"Weight"|"Cold")`:
- `ChipHeart` → the usable-HP / penalty boundary
- `ChipHunger` → centre of the hunger zone (hidden until `SetHunger > 0`)
- `ChipWeight` → centre of the weight zone
- `ChipCold` → centre of the cold zone
So when hunger/weight grow or shrink, the chips ride along with the zone edges.
Add your own marker by placing an `Image` in `ChipCanvas` and binding it (or extend
`PositionChips`). Swap the placeholder tints for real icon textures.
## 10. Notes / limits
- The bar is a UI-domain material — it only renders inside UMG/Slate (not on
meshes). Verify visuals in the UMG designer or content-browser thumbnail.
- Up to 4 penalty zones are drawn by the material in a single draw call; the
widget warns if you push more.
- `SetFillPercent` clamps to 0..1. Feed it `CurrentHP / MaxHP`, etc.

View File

@ -0,0 +1,345 @@
// Copyright IHY. Universal Bars plugin.
#include "UniversalBarWidget.h"
#include "Components/Image.h"
#include "Materials/MaterialInstanceDynamic.h"
UUniversalBarWidget::UUniversalBarWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UUniversalBarWidget::NativeConstruct()
{
Super::NativeConstruct();
EnsureMID();
if (BarMID)
{
BarMID->SetScalarParameterValue(TEXT("FillPercent"), DisplayFill);
BarMID->SetScalarParameterValue(TEXT("DelayedFillPercent"), DelayedFill);
}
PushZones(true);
}
UMaterialInstanceDynamic* UUniversalBarWidget::EnsureMID()
{
if (BarMID || !BarImage)
{
return BarMID;
}
BarMID = BarImage->GetDynamicMaterial();
return BarMID;
}
void UUniversalBarWidget::SetFillPercent(float NewValue, bool bAnimate)
{
EnsureMID();
NewValue = FMath::Clamp(NewValue, 0.f, 1.f);
if (!bAnimate)
{
TargetFill = DisplayFill = DelayedFill = NewValue;
if (BarMID)
{
BarMID->SetScalarParameterValue(TEXT("FillPercent"), DisplayFill);
BarMID->SetScalarParameterValue(TEXT("DelayedFillPercent"), DelayedFill);
}
return;
}
const bool bDown = NewValue < TargetFill - KINDA_SMALL_NUMBER;
const bool bUp = NewValue > TargetFill + KINDA_SMALL_NUMBER;
TargetFill = NewValue;
if (bDown)
{
// instant bright drop; the lighter tail holds, then drains (damage lag)
DisplayFill = NewValue;
DelayTimer = DelayedDelay;
if (BarMID)
{
BarMID->SetScalarParameterValue(TEXT("FillPercent"), DisplayFill);
}
if (bAutoFlashOnChange)
{
PlayDamageFlash();
}
}
else if (bUp)
{
// show the incoming amount immediately as the lighter band; bright fill eases up into it
DelayedFill = FMath::Max(DelayedFill, NewValue);
if (BarMID)
{
BarMID->SetScalarParameterValue(TEXT("DelayedFillPercent"), DelayedFill);
}
if (bAutoFlashOnChange)
{
PlayHealFlash();
}
}
}
void UUniversalBarWidget::SetShieldPercent(float NewValue)
{
EnsureMID();
if (BarMID)
{
BarMID->SetScalarParameterValue(TEXT("ShieldPercent"), FMath::Clamp(NewValue, 0.f, 1.f));
}
}
void UUniversalBarWidget::PlayDamageFlash()
{
PlayFlash(DamageFlashColor);
}
void UUniversalBarWidget::PlayHealFlash()
{
PlayFlash(HealFlashColor);
}
void UUniversalBarWidget::PlayFlash(FLinearColor Color)
{
EnsureMID();
PendingFlashColor = Color;
if (BarMID)
{
BarMID->SetVectorParameterValue(TEXT("FlashColor"), Color);
}
FlashAmount = 1.f;
bFlashing = true;
}
void UUniversalBarWidget::SetBarColors(FLinearColor Fill, FLinearColor Empty, FLinearColor Border, FLinearColor Delayed)
{
EnsureMID();
if (!BarMID)
{
return;
}
BarMID->SetVectorParameterValue(TEXT("FillColor"), Fill);
BarMID->SetVectorParameterValue(TEXT("EmptyColor"), Empty);
BarMID->SetVectorParameterValue(TEXT("BorderColor"), Border);
BarMID->SetVectorParameterValue(TEXT("DelayedFillColor"), Delayed);
}
void UUniversalBarWidget::SetSegmentCount(int32 Count)
{
EnsureMID();
if (!BarMID)
{
return;
}
BarMID->SetScalarParameterValue(TEXT("SegmentCount"), static_cast<float>(FMath::Max(0, Count)));
BarMID->SetScalarParameterValue(TEXT("SegmentDividerOpacity"), Count > 0 ? 0.85f : 0.f);
}
void UUniversalBarWidget::SetPenaltyZone(FName Id, float Width, FLinearColor Color)
{
Width = FMath::Clamp(Width, 0.f, 1.f);
for (FBarZone& Zone : PenaltyZones)
{
if (Zone.Id == Id)
{
Zone.Width = Width;
Zone.Color = Color;
return; // tick eases DisplayWidth toward the new Width
}
}
FBarZone& NewZone = PenaltyZones[PenaltyZones.AddDefaulted()];
NewZone.Id = Id;
NewZone.Width = Width;
NewZone.Color = Color;
}
void UUniversalBarWidget::SetPenaltyZoneWidth(FName Id, float Width)
{
Width = FMath::Clamp(Width, 0.f, 1.f);
for (FBarZone& Zone : PenaltyZones)
{
if (Zone.Id == Id)
{
Zone.Width = Width;
return;
}
}
}
void UUniversalBarWidget::RemovePenaltyZone(FName Id)
{
// Ease to zero, then drop on next tick when fully collapsed.
for (FBarZone& Zone : PenaltyZones)
{
if (Zone.Id == Id)
{
Zone.Width = 0.f;
return;
}
}
}
void UUniversalBarWidget::ClearPenaltyZones()
{
for (FBarZone& Zone : PenaltyZones)
{
Zone.Width = 0.f;
}
}
void UUniversalBarWidget::PushZones(bool bImmediate)
{
EnsureMID();
if (!BarMID)
{
return;
}
const int32 Count = FMath::Min(PenaltyZones.Num(), MaxMaterialZones);
float Total = 0.f;
for (int32 i = 0; i < Count; ++i)
{
if (bImmediate)
{
PenaltyZones[i].DisplayWidth = PenaltyZones[i].Width;
}
Total += FMath::Clamp(PenaltyZones[i].DisplayWidth, 0.f, 1.f);
}
Total = FMath::Clamp(Total, 0.f, 1.f);
const float MaxFill = 1.f - Total;
BarMID->SetScalarParameterValue(TEXT("MaxFillPercent"), MaxFill);
BarMID->SetScalarParameterValue(TEXT("PenaltyCount"), static_cast<float>(Count));
float Bound = MaxFill;
static const TCHAR* EndNames[MaxMaterialZones] = { TEXT("PenaltyEnd1"), TEXT("PenaltyEnd2"), TEXT("PenaltyEnd3"), TEXT("PenaltyEnd4") };
static const TCHAR* ColorNames[MaxMaterialZones] = { TEXT("PenaltyColor1"), TEXT("PenaltyColor2"), TEXT("PenaltyColor3"), TEXT("PenaltyColor4") };
for (int32 i = 0; i < MaxMaterialZones; ++i)
{
if (i < Count)
{
Bound = FMath::Min(1.f, Bound + FMath::Clamp(PenaltyZones[i].DisplayWidth, 0.f, 1.f));
BarMID->SetScalarParameterValue(EndNames[i], Bound);
BarMID->SetVectorParameterValue(ColorNames[i], PenaltyZones[i].Color);
}
else
{
BarMID->SetScalarParameterValue(EndNames[i], 1.f);
}
}
}
float UUniversalBarWidget::GetUsableEnd() const
{
float Total = 0.f;
const int32 N = FMath::Min(PenaltyZones.Num(), MaxMaterialZones);
for (int32 i = 0; i < N; ++i)
{
Total += FMath::Clamp(PenaltyZones[i].DisplayWidth, 0.f, 1.f);
}
return FMath::Clamp(1.f - Total, 0.f, 1.f);
}
float UUniversalBarWidget::GetZoneCenter(FName Id) const
{
float Edge = GetUsableEnd();
const int32 N = FMath::Min(PenaltyZones.Num(), MaxMaterialZones);
for (int32 i = 0; i < N; ++i)
{
const float W = FMath::Clamp(PenaltyZones[i].DisplayWidth, 0.f, 1.f);
if (PenaltyZones[i].Id == Id)
{
return Edge + W * 0.5f;
}
Edge += W;
}
return -1.f;
}
void UUniversalBarWidget::SetScalar(FName Param, float Value)
{
EnsureMID();
if (BarMID) { BarMID->SetScalarParameterValue(Param, Value); }
}
void UUniversalBarWidget::SetVector(FName Param, FLinearColor Value)
{
EnsureMID();
if (BarMID) { BarMID->SetVectorParameterValue(Param, Value); }
}
void UUniversalBarWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
Super::NativeTick(MyGeometry, InDeltaTime);
if (!BarMID)
{
return;
}
// bright fill eases toward target (smooth rise on heal; already snapped on damage)
if (!FMath::IsNearlyEqual(DisplayFill, TargetFill, 1e-3f))
{
DisplayFill = FMath::FInterpTo(DisplayFill, TargetFill, InDeltaTime, FillRiseSpeed);
BarMID->SetScalarParameterValue(TEXT("FillPercent"), DisplayFill);
}
// lighter "lag" tail: holds, then drains toward target (damage). Never below the bright fill.
const float DelayedTarget = FMath::Max(TargetFill, DisplayFill);
if (!FMath::IsNearlyEqual(DelayedFill, DelayedTarget, 1e-3f))
{
if (DelayTimer > 0.f)
{
DelayTimer -= InDeltaTime;
}
else
{
DelayedFill = FMath::FInterpTo(DelayedFill, DelayedTarget, InDeltaTime, DelayedFallSpeed);
BarMID->SetScalarParameterValue(TEXT("DelayedFillPercent"), DelayedFill);
}
}
// flash decay
if (bFlashing)
{
FlashAmount = FMath::FInterpTo(FlashAmount, 0.f, InDeltaTime, FlashSpeed);
BarMID->SetScalarParameterValue(TEXT("FlashAmount"), FlashAmount);
if (FlashAmount < 0.01f)
{
FlashAmount = 0.f;
bFlashing = false;
BarMID->SetScalarParameterValue(TEXT("FlashAmount"), 0.f);
}
}
// low-value pulse
const float PulseTarget = (TargetFill < LowThreshold) ? 1.f : 0.f;
if (!FMath::IsNearlyEqual(PulseAmount, PulseTarget, 1e-3f))
{
PulseAmount = FMath::FInterpTo(PulseAmount, PulseTarget, InDeltaTime, 6.f);
BarMID->SetScalarParameterValue(TEXT("LowHPPulseAmount"), PulseAmount);
}
// penalty zone widths ease toward target; drop fully-collapsed removed zones
bool bZonesDirty = false;
bool bNeedCompact = false;
for (FBarZone& Zone : PenaltyZones)
{
if (!FMath::IsNearlyEqual(Zone.DisplayWidth, Zone.Width, 1e-4f))
{
Zone.DisplayWidth = FMath::FInterpTo(Zone.DisplayWidth, Zone.Width, InDeltaTime, ZoneInterpSpeed);
bZonesDirty = true;
}
if (Zone.Width <= 0.f && Zone.DisplayWidth <= 1e-3f)
{
bNeedCompact = true;
}
}
if (bZonesDirty)
{
PushZones(false);
}
if (bNeedCompact)
{
PenaltyZones.RemoveAll([](const FBarZone& Z) { return Z.Width <= 0.f && Z.DisplayWidth <= 1e-3f; });
PushZones(false);
}
}

View File

@ -0,0 +1,4 @@
// Copyright IHY. Universal Bars plugin.
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, UniversalBars);

View File

@ -0,0 +1,59 @@
// Copyright IHY. Universal Bars plugin.
#include "UniversalBarsSubsystem.h"
#include "UniversalHUDWidget.h"
#include "Blueprint/UserWidget.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
UUniversalBarsSubsystem* UUniversalBarsSubsystem::Get(const UObject* WorldContextObject)
{
if (!WorldContextObject)
{
return nullptr;
}
if (const UWorld* World = WorldContextObject->GetWorld())
{
return World->GetSubsystem<UUniversalBarsSubsystem>();
}
return nullptr;
}
UUniversalHUDWidget* UUniversalBarsSubsystem::CreateHUD(APlayerController* Owner, TSubclassOf<UUniversalHUDWidget> HUDClass, int32 ZOrder)
{
if (!HUDClass)
{
return nullptr;
}
UUniversalHUDWidget* Widget = CreateWidget<UUniversalHUDWidget>(Owner ? Cast<APlayerController>(Owner) : GetWorld()->GetFirstPlayerController(), HUDClass);
if (Widget)
{
Widget->AddToViewport(ZOrder);
RegisterHUD(Widget); // also registered in NativeConstruct; idempotent
}
return Widget;
}
void UUniversalBarsSubsystem::RegisterHUD(UUniversalHUDWidget* InHUD)
{
if (InHUD)
{
HUD = InHUD;
}
}
void UUniversalBarsSubsystem::UnregisterHUD(UUniversalHUDWidget* InHUD)
{
if (HUD.Get() == InHUD)
{
HUD.Reset();
}
}
void UUniversalBarsSubsystem::SetHealth(float Percent) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetHealth(Percent); } }
void UUniversalBarsSubsystem::SetStamina(float Percent) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetStamina(Percent); } }
void UUniversalBarsSubsystem::SetShield(float Percent) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetShield(Percent); } }
void UUniversalBarsSubsystem::SetHunger(float Amount) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetHunger(Amount); } }
void UUniversalBarsSubsystem::SetWeight(float Amount) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetWeight(Amount); } }
void UUniversalBarsSubsystem::SetCold(float Amount) { if (UUniversalHUDWidget* H = HUD.Get()) { H->SetCold(Amount); } }
void UUniversalBarsSubsystem::DamageHealth(float Delta) { if (UUniversalHUDWidget* H = HUD.Get()) { H->DamageHealth(Delta); } }
void UUniversalBarsSubsystem::HealHealth(float Delta) { if (UUniversalHUDWidget* H = HUD.Get()) { H->HealHealth(Delta); } }

View 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();
}
}

View File

@ -0,0 +1,157 @@
// Copyright IHY. Universal Bars plugin.
// One animated UMG bar driven by M_UI_UniversalBar. Handles a single logical
// value (HP, stamina, durability, ...) plus optional data-driven penalty zones
// for combined PEAK-style capacity bars (hunger / weight / cold eating into max).
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UniversalBarWidget.generated.h"
class UImage;
class UMaterialInstanceDynamic;
// A penalty / reserve zone consuming capacity from the right end of the bar.
USTRUCT(BlueprintType)
struct FBarZone
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Zone")
FName Id = NAME_None;
// Target width as a fraction of the full track (0..1).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Zone", meta=(ClampMin="0", ClampMax="1"))
float Width = 0.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Zone")
FLinearColor Color = FLinearColor(0.9f, 0.6f, 0.2f, 1.f);
// Animated width that eases toward Width at runtime (not authored).
float DisplayWidth = 0.f;
};
UCLASS()
class UNIVERSALBARS_API UUniversalBarWidget : public UUserWidget
{
GENERATED_BODY()
public:
UUniversalBarWidget(const FObjectInitializer& ObjectInitializer);
// Name this "BarImage" in the WBP; assign an MI_UI_Bar_* (or the master) to its Brush.
UPROPERTY(meta=(BindWidget))
TObjectPtr<UImage> BarImage = nullptr;
// ---- animation tuning ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float FillRiseSpeed = 7.f; // how fast the bright fill grows on increase/heal
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float DelayedFallSpeed = 4.f; // how fast the damage-lag tail drains on decrease
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float DelayedDelay = 0.25f; // pause before the tail starts draining (sec)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float FlashSpeed = 6.f; // flash decay rate
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float ZoneInterpSpeed = 8.f; // penalty-zone width easing
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
float LowThreshold = 0.25f; // below this, low-value pulse turns on
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Anim")
bool bAutoFlashOnChange = true; // SetFillPercent auto-plays damage/heal flash
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Colors")
FLinearColor DamageFlashColor = FLinearColor(1.f, 0.12f, 0.12f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Colors")
FLinearColor HealFlashColor = FLinearColor(0.6f, 1.f, 0.6f, 1.f);
// ---- data-driven penalty zones (combined / PEAK mode) ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Bar|Zones")
TArray<FBarZone> PenaltyZones;
// ===== public API =====
// Animated set of the bar's main value (0..1). Decrease => instant drop + lag
// tail (+ damage flash). Increase => smooth rise (+ heal flash).
UFUNCTION(BlueprintCallable, Category="Bar")
void SetFillPercent(float NewValue, bool bAnimate = true);
UFUNCTION(BlueprintCallable, Category="Bar")
void SetShieldPercent(float NewValue);
UFUNCTION(BlueprintCallable, Category="Bar")
void PlayDamageFlash();
UFUNCTION(BlueprintCallable, Category="Bar")
void PlayHealFlash();
UFUNCTION(BlueprintCallable, Category="Bar")
void PlayFlash(FLinearColor Color);
UFUNCTION(BlueprintCallable, Category="Bar")
void SetBarColors(FLinearColor Fill, FLinearColor Empty, FLinearColor Border, FLinearColor Delayed);
UFUNCTION(BlueprintCallable, Category="Bar")
void SetSegmentCount(int32 Count);
// ---- penalty zone management (animated) ----
UFUNCTION(BlueprintCallable, Category="Bar|Zones")
void SetPenaltyZone(FName Id, float Width, FLinearColor Color);
UFUNCTION(BlueprintCallable, Category="Bar|Zones")
void SetPenaltyZoneWidth(FName Id, float Width);
UFUNCTION(BlueprintCallable, Category="Bar|Zones")
void RemovePenaltyZone(FName Id);
UFUNCTION(BlueprintCallable, Category="Bar|Zones")
void ClearPenaltyZones();
// Generic escape hatches.
UFUNCTION(BlueprintCallable, Category="Bar")
void SetScalar(FName Param, float Value);
UFUNCTION(BlueprintCallable, Category="Bar")
void SetVector(FName Param, FLinearColor Value);
UFUNCTION(BlueprintPure, Category="Bar")
float GetFillPercent() const { return TargetFill; }
// ---- zone layout in bar-U space (0..1), using current animated widths ----
// Right edge of the usable region (1 - sum of penalty display widths).
UFUNCTION(BlueprintPure, Category="Bar|Zones")
float GetUsableEnd() const;
// U-coordinate of a penalty zone's centre; -1 if the zone is absent.
UFUNCTION(BlueprintPure, Category="Bar|Zones")
float GetZoneCenter(FName Id) const;
protected:
virtual void NativeConstruct() override;
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
UMaterialInstanceDynamic* EnsureMID();
void PushZones(bool bImmediate);
private:
UPROPERTY(Transient)
TObjectPtr<UMaterialInstanceDynamic> BarMID = nullptr;
float TargetFill = 1.f;
float DisplayFill = 1.f;
float DelayedFill = 1.f;
float DelayTimer = 0.f;
float FlashAmount = 0.f;
bool bFlashing = false;
FLinearColor PendingFlashColor = FLinearColor::Red;
float PulseAmount = 0.f;
static constexpr int32 MaxMaterialZones = 4;
};

View File

@ -0,0 +1,50 @@
// Copyright IHY. Universal Bars plugin.
// Global access point for the active HUD. From any Blueprint/C++:
// Get World Subsystem -> UniversalBarsSubsystem -> Set Health (0.5)
// No widget reference needed. The HUD widget registers itself on construct.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "UniversalBarsSubsystem.generated.h"
class UUniversalHUDWidget;
class APlayerController;
UCLASS()
class UNIVERSALBARS_API UUniversalBarsSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
// One-call accessor from any object with a world.
UFUNCTION(BlueprintPure, Category="UniversalBars", meta=(WorldContext="WorldContextObject"))
static UUniversalBarsSubsystem* Get(const UObject* WorldContextObject);
// Spawn a HUD widget, add it to the viewport, and make it the active HUD.
UFUNCTION(BlueprintCallable, Category="UniversalBars")
UUniversalHUDWidget* CreateHUD(APlayerController* Owner, TSubclassOf<UUniversalHUDWidget> HUDClass, int32 ZOrder = 0);
UFUNCTION(BlueprintCallable, Category="UniversalBars")
void RegisterHUD(UUniversalHUDWidget* InHUD);
UFUNCTION(BlueprintCallable, Category="UniversalBars")
void UnregisterHUD(UUniversalHUDWidget* InHUD);
UFUNCTION(BlueprintPure, Category="UniversalBars")
UUniversalHUDWidget* GetHUD() const { return HUD.Get(); }
// ===== global forwarders (animated) =====
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetHealth(float Percent);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetStamina(float Percent);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetShield(float Percent);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetHunger(float Amount);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetWeight(float Amount);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void SetCold(float Amount);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void DamageHealth(float Delta);
UFUNCTION(BlueprintCallable, Category="UniversalBars") void HealHealth(float Delta);
private:
UPROPERTY()
TWeakObjectPtr<UUniversalHUDWidget> HUD;
};

View File

@ -0,0 +1,36 @@
// Copyright IHY. Universal Bars plugin.
// Central palette / geometry tokens for the HUD. One DataAsset to retint every
// bar and chip without touching widgets. Applied by UUniversalHUDWidget on construct.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "UniversalBarsTheme.generated.h"
UCLASS(BlueprintType)
class UNIVERSALBARS_API UUniversalBarsTheme : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
// ---- palette ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor HealthFill = FLinearColor(0.30f, 0.85f, 0.20f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor StaminaFill = FLinearColor(0.85f, 0.95f, 0.30f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Hunger = FLinearColor(0.90f, 0.60f, 0.20f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Weight = FLinearColor(0.82f, 0.23f, 0.23f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Cold = FLinearColor(0.40f, 0.70f, 1.00f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Shield = FLinearColor(0.65f, 0.85f, 1.00f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Border = FLinearColor(1.00f, 1.00f, 1.00f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Empty = FLinearColor(0.05f, 0.07f, 0.05f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor Delayed = FLinearColor(1.00f, 0.95f, 0.85f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor DamageFlash = FLinearColor(1.00f, 0.12f, 0.12f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Palette") FLinearColor HealFlash = FLinearColor(0.60f, 1.00f, 0.60f, 1.f);
// ---- geometry (PEAK = thick white border, soft rounding) ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Geometry", meta=(ClampMin="0", ClampMax="1")) float CornerRadius = 0.60f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Geometry", meta=(ClampMin="0", ClampMax="0.25")) float BorderSize = 0.10f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Geometry", meta=(ClampMin="0", ClampMax="0.2")) float InnerPadding = 0.06f;
// ---- status chips ----
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Chips") TArray<FLinearColor> ChipColors;
};

View File

@ -0,0 +1,115 @@
// Copyright IHY. Universal Bars plugin.
// HUD container that stacks a combined health bar (HP + hunger/weight/cold
// penalty zones) and a stamina bar. Exposes high-level, animated stat setters
// and registers itself with UUniversalBarsSubsystem for global access.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "UniversalHUDWidget.generated.h"
class UUniversalBarWidget;
class UPanelWidget;
class UCanvasPanel;
class UImage;
class UUniversalBarsTheme;
UCLASS()
class UNIVERSALBARS_API UUniversalHUDWidget : public UUserWidget
{
GENERATED_BODY()
public:
// Place a WBP_UniversalBar named "HealthBar" / "StaminaBar" in the HUD WBP.
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UUniversalBarWidget> HealthBar = nullptr;
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UUniversalBarWidget> StaminaBar = nullptr;
// Optional container for status chips (filled in the designer with placeholders).
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UPanelWidget> ChipsPanel = nullptr;
// PEAK-style markers that sit ABOVE the health bar, over their zone. Place
// these inside a CanvasPanel ("ChipCanvas") overlaid on HealthBar; they are
// moved every tick to track the (animated) zone boundaries.
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UCanvasPanel> ChipCanvas = nullptr;
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UImage> ChipHeart = nullptr; // tracks usable-HP end
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UImage> ChipHunger = nullptr; // tracks "Hunger" zone
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UImage> ChipWeight = nullptr; // tracks "Weight" zone
UPROPERTY(meta=(BindWidgetOptional))
TObjectPtr<UImage> ChipCold = nullptr; // tracks "Cold" zone (hidden if absent)
// Optional central palette/geometry; applied on construct when set.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD")
TObjectPtr<UUniversalBarsTheme> Theme = nullptr;
// Penalty-zone colours used by the hunger/weight/cold helpers (overridden by Theme if set).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD|Colors")
FLinearColor HungerColor = FLinearColor(0.90f, 0.60f, 0.20f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD|Colors")
FLinearColor WeightColor = FLinearColor(0.82f, 0.23f, 0.23f, 1.f);
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD|Colors")
FLinearColor ColdColor = FLinearColor(0.40f, 0.70f, 1.00f, 1.f);
// ===== high-level animated API =====
UFUNCTION(BlueprintCallable, Category="HUD") void SetHealth(float Percent); // 0..1
UFUNCTION(BlueprintCallable, Category="HUD") void SetStamina(float Percent); // 0..1
UFUNCTION(BlueprintCallable, Category="HUD") void SetShield(float Percent); // 0..1 (overlay above HP)
// Penalty fractions of the full track (eat into max HP). 0 hides the zone.
UFUNCTION(BlueprintCallable, Category="HUD") void SetHunger(float Amount);
UFUNCTION(BlueprintCallable, Category="HUD") void SetWeight(float Amount);
UFUNCTION(BlueprintCallable, Category="HUD") void SetCold(float Amount);
// Deltas (positive = heal/up, negative = damage/down) with the right flash.
UFUNCTION(BlueprintCallable, Category="HUD") void DamageHealth(float Delta);
UFUNCTION(BlueprintCallable, Category="HUD") void HealHealth(float Delta);
UFUNCTION(BlueprintPure, Category="HUD") float GetHealth() const;
// Re-tint and re-shape all bars + chips from the assigned Theme.
UFUNCTION(BlueprintCallable, Category="HUD") void ApplyTheme();
// ===== self-test / study driver =====
// When on, NativeTick cycles through every stat change so you can watch the
// animations live in PIE. Defaults ON for this study HUD — uncheck "Demo Mode"
// in the WBP Class Defaults (or call SetDemoMode(false)) for shipping.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD|Demo")
bool bDemoMode = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="HUD|Demo", meta=(ClampMin="0.2"))
float DemoStepInterval = 1.6f;
UFUNCTION(BlueprintCallable, Category="HUD|Demo")
void SetDemoMode(bool bEnable);
// Runs one demo step (damage / heal / hunger / weight / stamina). Called by
// the demo tick, but also handy to fire manually from BP.
UFUNCTION(BlueprintCallable, Category="HUD|Demo")
void DemoStepNow();
protected:
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
// Slides each chip to sit above the centre of its zone on the health bar.
void PositionChips();
private:
float DemoTimer = 0.f;
int32 DemoStep = 0;
bool bDemoInit = false;
};

View File

@ -0,0 +1,20 @@
// Copyright IHY. Universal Bars plugin.
using UnrealBuildTool;
public class UniversalBars : ModuleRules
{
public UniversalBars(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"UMG",
"SlateCore",
"Slate"
});
}
}

19
UniversalBars.uplugin Normal file
View File

@ -0,0 +1,19 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "Universal Bars",
"Description": "PEAK-style universal UMG bars (health / stamina / hunger / weight / shield / boss / durability) driven by one procedural material, with a global subsystem API and animated damage / heal / low-HP feedback.",
"Category": "UI",
"CreatedBy": "IHY",
"CanContainContent": true,
"IsBetaVersion": false,
"Installed": false,
"Modules": [
{
"Name": "UniversalBars",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}