UI component for the FPS health bar. HealthView is a stateless presenter that builds the track with fill, ghost chip, numeric display and flash overlays. HealthWidget is a ScreenPanel widget that owns a HealthModel and theme, exposes Damage/Heal methods, ticks the model each frame, and builds the UI root.
using System;
using Goo;
using Sandbox;
using Sandbox.UI;
namespace Goo.FpsUI;
// Stateless presenter: the health bar with ghost chip, flashes, and low-HP pulse.
static class HealthView
{
public const float TrackW = 320f, TrackH = 24f;
public static Container Build( HealthModel m, FpsTheme t )
{
float frac = m.ShownFraction, ghost = m.GhostFraction;
Color grade = Color.Lerp( t.Warn, t.Good, Math.Clamp( frac, 0f, 1f ) );
float pulse = m.Critical ? 0.10f + 0.14f * (MathF.Sin( Time.Now * 7f ) * 0.5f + 0.5f) : 0f;
var track = new Container
{
Key = "hpTrack", Position = PositionMode.Relative,
Width = TrackW, Height = TrackH, BackgroundColor = t.TrackBg,
BorderRadius = t.Radius, Overflow = OverflowMode.Hidden,
Children =
{
Parts.FillRect( "ghost", ghost, Color.White.WithAlpha( 0.5f ), t.Radius ),
Parts.FillRect( "fill", frac, grade, t.Radius ),
},
};
if ( pulse > 0f ) track.Children.Add( Parts.Overlay( "warn", t.Warn, pulse, t.Radius ) );
if ( m.HealFlash > 0.001f ) track.Children.Add( Parts.Overlay( "heal", t.Good, m.HealFlash * 0.7f, t.Radius ) );
if ( m.HitFlash > 0.001f ) track.Children.Add( Parts.Overlay( "hit", Color.White, m.HitFlash * 0.9f, t.Radius ) );
track.Children.Add( new Container
{
Key = "num", Position = PositionMode.Absolute, Top = 0, Left = 0,
Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
JustifyContent = Justify.Center, AlignItems = Align.Center,
Children = { new Text( $"{m.Number}/{(int)m.MaxHealth}" )
{ FontFamily = t.FontFamily, FontSize = 14f, FontWeight = t.WeightBold, FontColor = Color.White } },
} );
return track;
}
}
// Standalone health bar. Drop on a ScreenPanel GameObject, call Damage/Heal from game code.
public sealed partial class HealthWidget : GooPanel<Container>
{
[Property, Range( 1f, 1000f )] public float MaxHealth { get; set; } = 100f; // full-bar value
readonly HealthModel _m = new();
readonly FpsTheme _t = new();
bool _booted;
void Boot() { _m.MaxHealth = MaxHealth; _m.Reset(); _booted = true; }
public void Damage( float amount ) => _m.Damage( amount ); // apply damage from your game
public void Heal( float amount ) => _m.Heal( amount ); // apply healing from your game
// Demo-only seam: implemented in FpsDemo.cs, compiles out when that file is deleted.
partial void StepDemo( float dt, ref bool active );
protected override bool Tick( float dt )
{
if ( !_booted ) Boot();
bool demo = false;
StepDemo( dt, ref demo );
bool moving = _m.Tick( dt );
return demo || moving || _m.Critical;
}
protected override Container Build()
{
if ( !_booted ) Boot();
var root = Parts.Root( "fpsHealth" );
root.Children.Add( Parts.Anchor( "a", Parts.Corner.BottomLeft, _t.Margin, Parts.Panel( "bg", _t, HealthView.Build( _m, _t ) ) ) );
return root;
}
}