A Razor UI component that renders a world-space unit healthbar. It computes colors, fill percentages, armor display and positions the WorldPanel based on camera / FPS mode, then outputs HTML with inline styles reflecting health, delta and armor.
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits PanelComponent
@attribute [StyleSheet("UnitHealthbar.razor.scss")]
@{
if (!Unit.IsValid() || Manager.Instance == null)
return;
Player player = Unit as Player;
Color bgColor = player.IsValid() && !(player.Health > 1f)
? Color.Lerp(new Color(0f, 0f, 0f, 0.8f), new Color(0.5f, 0f, 0f, 0.8f), Utils.FastSin(RealTime.Now * 16f))
: new Color(0f, 0f, 0f, 0.8f);
float fillHpPercent = player.IsValid() ? Math.Max(Unit.HpPercent, 0.02f) : Unit.HpPercent;
bool hpBarHidden = player.IsValid() && player == Manager.Instance.LocalPlayer && player.Stats[ PlayerStat.HpBarHidden ] > 0f;
// Round so near-1 HpPercent (float imprecision) can't yield a tiny ~2.98e-5 value that
// .NET formats in scientific notation (e.g. "2.9802322E-05"), which the style parser rejects.
float deltaRight = MathF.Round( (1f - Unit.HpPercent) * 100f, 3 );
float fillRight = MathF.Round( (1f - fillHpPercent) * 100f, 3 );
}
<root style="opacity:@(Unit.IsDying || Manager.Instance.GameState == GameState.Lobby ? 0f : 1f);">
<div class="container">
<div class="health" style="opacity:@(Unit.HealthbarOpacity)">
<div class="healthbar" style="background-color: @bgColor.Rgba;">
@if ( hpBarHidden )
{
<div class="heathbarfill" style="right: 0%; background-color: #7a0040ff;"></div>
}
else
{
<div class="heathbardelta" style="right:@(deltaRight)%;"></div>
<div class="heathbarfill" style="right:@(fillRight)%;"></div>
}
</div>
</div>
@if(Unit.Armor > 0)
{
var color = Color.Lerp(new Color(1f, 0.8f, 0.8f, 1f), new Color(0.65f, 0.65f, 0.65f, 0.75f), Utils.Map(Unit.RealTimeSinceArmorChanged, 0f, 0.5f, 0f, 1f, EasingType.QuadOut));
var opacity = Utils.Map(Unit.RealTimeSinceArmorChanged, 0f, 0.5f, 1f, 0.5f, EasingType.QuadOut);
@* var fontSize = Utils.Map(Unit.RealTimeSinceArmorChanged, 0f, 0.5f, 80f, 60f, EasingType.QuadOut); *@
var scale = Utils.Map(Unit.RealTimeSinceArmorChanged, 0f, 0.5f, 1.3f, 1f, EasingType.QuadOut);
<label class="armor_text" text="@($"{(int)Unit.Armor}⛊")" style="color:@(color.Rgba); opacity: @(opacity * Unit.HealthbarArmorOpacity); transform: scale(@scale);" />
}
</div>
</root>
@code {
[Property] public Sandbox.WorldPanel WorldPanel { get; set; }
public Unit Unit { get; set; }
// public float Opacity { get; set; }
protected override void OnStart()
{
base.OnStart();
}
protected override void OnUpdate()
{
base.OnUpdate();
if (!Unit.IsValid())
Unit = GetComponentInParent<Unit>();
if (!Unit.IsValid())
return;
WorldPanel.PanelSize = new Vector2(Unit.HealthbarWidth, 500);
var localPlayer = Manager.Instance.LocalPlayer;
bool fpsMode = localPlayer.IsValid() && localPlayer.GetSyncStat( PlayerStat.FpsMode ) > 0f;
if ( fpsMode )
{
var cam = Manager.Instance.Camera;
var toCamera = cam.WorldPosition - WorldPanel.WorldPosition;
WorldPanel.WorldRotation = Rotation.LookAt( toCamera, Vector3.Up );
if(Unit == localPlayer)
WorldPanel.LocalPosition = Vector3.Up * Unit.HealthbarOffset * 0.3f;
else
WorldPanel.LocalPosition = Vector3.Up * Unit.HealthbarOffset;
}
else
{
WorldPanel.WorldRotation = Rotation.From( -55f, -90f, 0f );
WorldPanel.LocalPosition = Vector3.Up * Unit.HealthbarOffset;
}
}
protected override int BuildHash()
{
if (!Unit.IsValid())
return 0;
var player = Unit as Player;
var armor = Unit.Armor;
var sinceArmorChange = Unit.RealTimeSinceArmorChanged.Relative < 0.5f ? Unit.RealTimeSinceArmorChanged.Relative : 0f;
var oneHpHash = player.IsValid() && !(player.Health > 1f) ? RealTime.Now : 0f;
bool hpBarHidden = player.IsValid() && player == Manager.Instance.LocalPlayer && player.Stats[PlayerStat.HpBarHidden] > 0f;
return HashCode.Combine(Unit.HpPercent, armor, sinceArmorChange, oneHpHash, Unit.IsDying, Unit.HealthbarOpacity, Manager.Instance.GameState, hpBarHidden);
}
}