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:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# UE build artifacts
|
||||||
|
Intermediate/
|
||||||
|
Binaries/
|
||||||
|
DerivedDataCache/
|
||||||
|
Saved/
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.VC.db
|
||||||
|
.vs/
|
||||||
BIN
Content/Components/WBP_BarHealth.uasset
Normal file
BIN
Content/Components/WBP_BarHealth.uasset
Normal file
Binary file not shown.
BIN
Content/Components/WBP_BarStamina.uasset
Normal file
BIN
Content/Components/WBP_BarStamina.uasset
Normal file
Binary file not shown.
BIN
Content/Components/WBP_HUD_InGame.uasset
Normal file
BIN
Content/Components/WBP_HUD_InGame.uasset
Normal file
Binary file not shown.
BIN
Content/Components/WBP_UniversalBar.uasset
Normal file
BIN
Content/Components/WBP_UniversalBar.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_Boss.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_Boss.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_Health.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_Health.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_Injury.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_Injury.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_PeakCombined.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_PeakCombined.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_Shield.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_Shield.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/MI_UI_Bar_Stamina.uasset
Normal file
BIN
Content/Materials/MI_UI_Bar_Stamina.uasset
Normal file
Binary file not shown.
BIN
Content/Materials/M_UI_UniversalBar.uasset
Normal file
BIN
Content/Materials/M_UI_UniversalBar.uasset
Normal file
Binary file not shown.
BIN
Content/Theme/DA_UniversalBars_Default.uasset
Normal file
BIN
Content/Theme/DA_UniversalBars_Default.uasset
Normal file
Binary file not shown.
231
README.md
Normal file
231
README.md
Normal 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.06–0.10 for the toy/cartoon
|
||||||
|
look; `CornerRadius` 0.5–0.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.
|
||||||
345
Source/UniversalBars/Private/UniversalBarWidget.cpp
Normal file
345
Source/UniversalBars/Private/UniversalBarWidget.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
Source/UniversalBars/Private/UniversalBarsModule.cpp
Normal file
4
Source/UniversalBars/Private/UniversalBarsModule.cpp
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Copyright IHY. Universal Bars plugin.
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FDefaultModuleImpl, UniversalBars);
|
||||||
59
Source/UniversalBars/Private/UniversalBarsSubsystem.cpp
Normal file
59
Source/UniversalBars/Private/UniversalBarsSubsystem.cpp
Normal 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); } }
|
||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
157
Source/UniversalBars/Public/UniversalBarWidget.h
Normal file
157
Source/UniversalBars/Public/UniversalBarWidget.h
Normal 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;
|
||||||
|
};
|
||||||
50
Source/UniversalBars/Public/UniversalBarsSubsystem.h
Normal file
50
Source/UniversalBars/Public/UniversalBarsSubsystem.h
Normal 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;
|
||||||
|
};
|
||||||
36
Source/UniversalBars/Public/UniversalBarsTheme.h
Normal file
36
Source/UniversalBars/Public/UniversalBarsTheme.h
Normal 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;
|
||||||
|
};
|
||||||
115
Source/UniversalBars/Public/UniversalHUDWidget.h
Normal file
115
Source/UniversalBars/Public/UniversalHUDWidget.h
Normal 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;
|
||||||
|
};
|
||||||
20
Source/UniversalBars/UniversalBars.Build.cs
Normal file
20
Source/UniversalBars/UniversalBars.Build.cs
Normal 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
19
UniversalBars.uplugin
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user