UI/ZombieHealthBarsHud.razor

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.

File Access
@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;
    }
}