using AlienAttack.MonoGame.GameLoops; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; namespace AlienAttack.MonoGame.Hud; public record DrawHudContext : DrawContext { public int Health; public int Shield; } internal class Hud { private Texture2D _pixel; public Hud(Game game) { _pixel = new Texture2D(game.GraphicsDevice, 1, 1); _pixel.SetData(new[] { Color.White }); } public void Draw(DrawHudContext context) { DrawSideBars(context); } private void DrawSideBars(DrawHudContext context) { SpriteBatch sb = context.SpriteBatch; int screenW = context.ViewTransoform.ScreenWidth; int screenH = context.ViewTransoform.ScreenHeight; double totalSeconds = context.GameTime.TotalGameTime.TotalSeconds; int health = Math.Clamp(context.Health, 0, 100); int shield = Math.Clamp(context.Shield, 0, 100); int marginTop = 10; int marginBottom = 10; int segments = 100; int gap = 4; int barWidth = 10; // thickness of the colored segments int borderPad = 2; // thickness around the bar (border area) int borderW = barWidth + borderPad * 2; int barHeight = screenH - marginTop - marginBottom; int segHeight = Math.Max(1, (barHeight - gap * (segments - 1)) / segments); int usedHeight = segments * segHeight + (segments - 1) * gap; int barTopY = marginTop; int barBottomY = marginTop + usedHeight; // Put left / right bars near screen edges int leftBorderX = 8; int rightBorderX = screenW - 8 - borderW; // Colors (tweak as desired) Color healthOn = new Color(255, 140, 0); // orange Color shieldOn = new Color(60, 140, 255); // blue Color off = new Color(28, 28, 28); // empty segment base // Draw metallic frames (behind segments) //DrawMetallicFrame(sb, new Rectangle(leftBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2)); //DrawMetallicFrame(sb, new Rectangle(rightBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2)); // Segment drawing X starts inside the border int leftSegX = leftBorderX + borderPad; int rightSegX = rightBorderX + borderPad; // Low health pulse settings bool lowHealth = health <= 20; float pulse = lowHealth ? (0.85f + 0.15f * (float)Math.Sin(totalSeconds * 6.0)) // subtle wobble : 1f; DrawSegmentedBar( sb, leftSegX, barBottomY, barWidth, segHeight, gap, value0To100: health, onColor: healthOn, offColor: off, segments: segments, // pulse only on filled health segments pulseMultiplier: pulse, pulseFilledOnly: true ); DrawSegmentedBar( sb, rightSegX, barBottomY, barWidth, segHeight, gap, value0To100: shield, onColor: shieldOn, offColor: off, segments: segments, // no pulse for shield by default pulseMultiplier: 1f, pulseFilledOnly: true ); } private void DrawSegmentedBar( SpriteBatch sb, int x, int bottomY, int barWidth, int segHeight, int gap, int value0To100, Color onColor, Color offColor, int segments, float pulseMultiplier, bool pulseFilledOnly) { // Gradient: bottom darker, top brighter float minBrightness = 0.55f; float maxBrightness = 1.00f; for (int i = 0; i < segments; i++) { int y = bottomY - (i + 1) * segHeight - i * gap; // i=0 bottom, i=segments-1 top float t = i / (float)(segments - 1); float brightness = MathHelper.Lerp(minBrightness, maxBrightness, t); bool filled = i < value0To100; Color baseColor = filled ? onColor : offColor; // Apply pulse (usually only on the filled parts) if (pulseMultiplier != 1f && (!pulseFilledOnly || filled)) { brightness *= pulseMultiplier; brightness = Math.Min(brightness, 1.2f); // allow a tiny “pop”, still safe } Color finalColor = ScaleRgb(baseColor, brightness); sb.Draw(_pixel, new Rectangle(x, y, barWidth, segHeight), finalColor); // Optional: little “shine” line on filled segments (adds a nice DOS UI feel) // Draw a 1px highlight on the left edge of lit segments. if (filled && barWidth >= 3) { var shine = ScaleRgb(Color.White, 0.10f * brightness); sb.Draw(_pixel, new Rectangle(x, y, 1, segHeight), shine); } } } private static Color ScaleRgb(Color c, float mul) { mul = Math.Max(0f, mul); byte r = (byte)Math.Clamp((int)(c.R * mul), 0, 255); byte g = (byte)Math.Clamp((int)(c.G * mul), 0, 255); byte b = (byte)Math.Clamp((int)(c.B * mul), 0, 255); return new Color(r, g, b, c.A); } private void DrawMetallicFrame(SpriteBatch sb, Rectangle outer) { // “Metal” palette Color outerBorder = new Color(10, 10, 10); Color fill = new Color(45, 45, 45); Color highlight = new Color(90, 90, 90); // top/left Color shadow = new Color(20, 20, 20); // bottom/right // Outer border sb.Draw(_pixel, outer, outerBorder); // Inner fill var inner = new Rectangle(outer.X + 1, outer.Y + 1, outer.Width - 2, outer.Height - 2); if (inner.Width > 0 && inner.Height > 0) sb.Draw(_pixel, inner, fill); // Bevel lines (1px) // Top highlight sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, inner.Width, 1), highlight); // Left highlight sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, 1, inner.Height), highlight); // Bottom shadow sb.Draw(_pixel, new Rectangle(inner.X, inner.Bottom - 1, inner.Width, 1), shadow); // Right shadow sb.Draw(_pixel, new Rectangle(inner.Right - 1, inner.Y, 1, inner.Height), shadow); // Optional: an even tighter inner “lip” for extra metal feel var lip = new Rectangle(inner.X + 1, inner.Y + 1, inner.Width - 2, inner.Height - 2); if (lip.Width > 0 && lip.Height > 0) { Color lipDark = new Color(35, 35, 35); sb.Draw(_pixel, lip, lipDark); } } }