A UI Razor component that displays zombie health bars as panels each frame. It takes a frame description from ZombieHealthBars.Frame, reuses pools of Panels/Images/Labels, positions and styles them each update, and hides unused pooled elements.
@using Sandbox;
@using Sandbox.UI;
@using System.Collections.Generic;
@using NZombies;
@inherits PanelComponent
@*
THE ZOMBIE HEALTH BARS ON SCREEN (2026-10-06): what `ZombieHealthBars` says the bar is this frame (`ZombieHealthBars.Frame`:
boxes, pictures and lines of text in screen pixels, in drawing order), put up as panels.
⛔ PANELS, BECAUSE THE CAMERA'S PAINTER SHOWED NOTHING: the bar was painted with `CameraComponent.BeginHud()` first, from Update
and then from PreRender, and the player saw nothing either time though the bar was composed. This is the way `DamageNumbersHud`
and `PlayerTagsHud` already draw over the world, and they show.
⚠️ THEIR MACHINERY: a constant BuildHash, the elements made and kept from code (a pool for each kind: boxes, pictures, texts),
each placed every frame with Style.Left/Top/Width/Height in the panel's units (`ScaleFromScreen`), stacked by its place in the
frame (`ZIndex`), and the ones not needed this frame set to `display: none` rather than deleted.
*@
<root class="zhb"></root>
@code
{
/// <summary>⛔ CONSTANT ON PURPOSE (`DamageNumbersHud`'s reason): the elements move every frame, and a hash that followed them would rebuild the tree every frame.</summary>
protected override int BuildHash() => 0;
readonly List<Panel> _boxes = new();
readonly List<Image> _pictures = new();
readonly List<string> _pictureOf = new();
readonly List<Label> _texts = new();
protected override void OnUpdate()
{
if ( Panel is null ) return;
var frame = ZombieHealthBars.Frame;
var s = Panel.ScaleFromScreen;
int boxes = 0, pictures = 0, texts = 0;
for ( var i = 0; i < frame.Count; i++ )
{
var p = frame[i];
switch ( p.Kind )
{
case 0:
{
var box = Box( boxes++ );
Place( box, p.Rect, s, i );
box.Style.BackgroundColor = p.Color;
break;
}
case 1:
{
var n = pictures++;
var pic = Picture( n );
Place( pic, p.Rect, s, i );
if ( _pictureOf[n] != p.Image )
{
_pictureOf[n] = p.Image;
pic.SetTexture( p.Image );
}
break;
}
default:
{
var text = Text( texts++ );
Place( text, p.Rect, s, i );
if ( text.Text != p.Text ) text.Text = p.Text;
text.Style.FontSize = Length.Pixels( p.Size * s );
text.SetClass( "name", p.Look == "name" );
text.SetClass( "damage", p.Look == "damage" );
break;
}
}
}
Hide( _boxes, boxes );
Hide( _pictures, pictures );
Hide( _texts, texts );
}
Panel Box( int n )
{
while ( _boxes.Count <= n )
{
var box = new Panel();
box.AddClass( "zbox" );
Panel.AddChild( box );
_boxes.Add( box );
}
return _boxes[n];
}
Image Picture( int n )
{
while ( _pictures.Count <= n )
{
var pic = new Image();
pic.AddClass( "zpic" );
Panel.AddChild( pic );
_pictures.Add( pic );
_pictureOf.Add( "" );
}
return _pictures[n];
}
Label Text( int n )
{
while ( _texts.Count <= n )
{
var text = new Label();
text.AddClass( "ztext" );
Panel.AddChild( text );
_texts.Add( text );
}
return _texts[n];
}
/// <summary>A piece at its place, in the panel's units, over everything drawn before it this frame.</summary>
static void Place( Panel panel, Rect rect, float s, int order )
{
panel.Style.Display = DisplayMode.Flex;
panel.Style.Left = Length.Pixels( rect.Left * s );
panel.Style.Top = Length.Pixels( rect.Top * s );
panel.Style.Width = Length.Pixels( rect.Width * s );
panel.Style.Height = Length.Pixels( rect.Height * s );
panel.Style.ZIndex = order;
}
static void Hide<T>( List<T> pool, int used ) where T : Panel
{
for ( var i = used; i < pool.Count; i++ )
pool[i].Style.Display = DisplayMode.None;
}
}