From 42608a16e350d12becb7d5b009e4194e9b6b9207 Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sun, 21 Dec 2025 23:22:53 -0500 Subject: [PATCH] Added HUD drawing logic. --- AlientAttack.MonoGame/Content/Content.mgcb | 18 ++-- AlientAttack.MonoGame/GameLoops/GameLoop.cs | 85 ++++++++++++++++- .../Things/Bullets/MinigunBulletLarge.cs | 24 +++++ .../Things/Bullets/MinigunBulletMedium.cs | 24 +++++ .../Things/Bullets/MinigunBulletSmall.cs | 4 +- .../Things/Enemies/EnemyShip.cs | 6 ++ .../Things/Enemies/GreenEnemy.cs | 7 +- .../Things/Enemies/RedEnemy.cs | 7 +- .../Things/Enemies/TealEnemy.cs | 7 +- AlientAttack.MonoGame/Things/Player.cs | 93 +++++++++++++++---- .../Things/Weapons/Minigun.cs | 11 +-- 11 files changed, 247 insertions(+), 39 deletions(-) create mode 100644 AlientAttack.MonoGame/Things/Bullets/MinigunBulletLarge.cs create mode 100644 AlientAttack.MonoGame/Things/Bullets/MinigunBulletMedium.cs create mode 100644 AlientAttack.MonoGame/Things/Enemies/EnemyShip.cs diff --git a/AlientAttack.MonoGame/Content/Content.mgcb b/AlientAttack.MonoGame/Content/Content.mgcb index 0e5de6e..086a9a6 100644 --- a/AlientAttack.MonoGame/Content/Content.mgcb +++ b/AlientAttack.MonoGame/Content/Content.mgcb @@ -779,7 +779,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Laser_Large_png_processed.png +/build:Sprites/Laser_Large_png_processed.png;Sprites/Laser_Large.png #begin Sprites/Laser_Medium_png_processed.png /importer:TextureImporter @@ -791,7 +791,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Laser_Medium_png_processed.png +/build:Sprites/Laser_Medium_png_processed.png;Sprites/Laser_Medium.png #begin Sprites/Laser_Small_png_processed.png /importer:TextureImporter @@ -803,7 +803,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Laser_Small_png_processed.png +/build:Sprites/Laser_Small_png_processed.png;Sprites/Laser_Small.png #begin Sprites/Minigun_Large_png_processed.png /importer:TextureImporter @@ -851,7 +851,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Plasma_Large_png_processed.png +/build:Sprites/Plasma_Large_png_processed.png;Sprites/Plasma_Large.png #begin Sprites/Plasma_Medium_png_processed.png /importer:TextureImporter @@ -863,7 +863,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Plasma_Medium_png_processed.png +/build:Sprites/Plasma_Medium_png_processed.png;Sprites/Plasma_Medium.png #begin Sprites/Plasma_Small_png_processed.png /importer:TextureImporter @@ -875,7 +875,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Plasma_Small_png_processed.png +/build:Sprites/Plasma_Small_png_processed.png;Sprites/Plasma_Small.png #begin Sprites/Platform_01_png_processed.png /importer:TextureImporter @@ -1235,7 +1235,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Proton_Large_png_processed.png +/build:Sprites/Proton_Large_png_processed.png;Sprites/Proton_Large.png #begin Sprites/Proton_Medium_png_processed.png /importer:TextureImporter @@ -1247,7 +1247,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Proton_Medium_png_processed.png +/build:Sprites/Proton_Medium_png_processed.png;Sprites/Proton_Medium.png #begin Sprites/Proton_Small_png_processed.png /importer:TextureImporter @@ -1259,7 +1259,7 @@ /processorParam:ResizeToPowerOfTwo=False /processorParam:MakeSquare=False /processorParam:TextureFormat=Color -/build:Sprites/Proton_Small_png_processed.png +/build:Sprites/Proton_Small_png_processed.png;Sprites/Proton_Small.png #begin Sprites/Rotor_png_processed.png /importer:TextureImporter diff --git a/AlientAttack.MonoGame/GameLoops/GameLoop.cs b/AlientAttack.MonoGame/GameLoops/GameLoop.cs index 775f2c1..4e8d0aa 100644 --- a/AlientAttack.MonoGame/GameLoops/GameLoop.cs +++ b/AlientAttack.MonoGame/GameLoops/GameLoop.cs @@ -19,13 +19,17 @@ internal class GameLoop : GameLoopBase private Starfield _starfield; private Texture2D _pixel; + private Player _player; protected readonly List Sprites = []; public GameLoop(AlienAttackGame game) : base(game) { InitializeStarField(game); - Sprites.Add(new Player(game.ViewTransform.ScreenWidth / 2 - 32, game.ViewTransform.ScreenHeight - 64)); + + _player = new Player(game.ViewTransform.ScreenWidth / 2 - 32, game.ViewTransform.ScreenHeight - 64); + + Sprites.Add(_player); } private void InitializeStarField(AlienAttackGame game) @@ -56,6 +60,7 @@ internal class GameLoop : GameLoopBase { _starfield.Draw(SpriteBatch, _pixel); //DrawBackground(); + DrawHud(); DrawSprites(); } @@ -90,6 +95,84 @@ internal class GameLoop : GameLoopBase } } + private void DrawHud() + { + DrawSideBars(SpriteBatch, ViewTransform.ScreenWidth, ViewTransform.ScreenHeight, _player.Health, _player.Shield); + } + + private void DrawSideBars(SpriteBatch sb, int screenW, int screenH, int health, int shield) + { + health = Math.Clamp(health, 0, 100); + shield = Math.Clamp(shield, 0, 100); + + // Tweak these to taste to match “Raptor-ish” proportions + int marginTop = 8; + int marginBottom = 8; + int barWidth = 10; // thin like the screenshot + int gap = 4; // small gap between segments + int segments = 100; + + int barHeight = screenH - marginTop - marginBottom; + + // Compute segment height so 100 segments + gaps fit in the available height + // If your resolution is small, this could become 0; clamp to 1. + int segHeight = Math.Max(1, (barHeight - gap * (segments - 1)) / segments); + + // Recompute barHeight actually used by segments (so we can bottom-align perfectly) + int usedHeight = segments * segHeight + (segments - 1) * gap; + + // Position bars along left/right edges + int leftX = 6; // a few pixels from edge + int rightX = screenW - 6 - barWidth; + + int bottomY = marginTop + usedHeight; // bottom of the bar area + + // Colors + Color healthOn = new Color(255, 140, 0); // orange-ish + Color shieldOn = new Color(60, 140, 255); // blue-ish + Color off = new Color(30, 30, 30); // dark "empty" segment + + DrawSegmentedBar(sb, leftX, bottomY, barWidth, segHeight, gap, health, healthOn, off, segments); + DrawSegmentedBar(sb, rightX, bottomY, barWidth, segHeight, gap, shield, shieldOn, off, segments); + } + + private void DrawSegmentedBar( + SpriteBatch sb, + int x, + int bottomY, + int barWidth, + int segHeight, + int gap, + int value0To100, + Color onColor, + Color offColor, + int segments = 100) + { + 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); + + Color baseColor = (i < value0To100) ? onColor : offColor; + + // Apply brightness scaling + Color finalColor = new Color( + (byte)(baseColor.R * brightness), + (byte)(baseColor.G * brightness), + (byte)(baseColor.B * brightness), + baseColor.A + ); + + sb.Draw(_pixel, new Rectangle(x, y, barWidth, segHeight), finalColor); + } + } + private void DrawSprites() { SpriteDrawArgs args = new(Game); diff --git a/AlientAttack.MonoGame/Things/Bullets/MinigunBulletLarge.cs b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletLarge.cs new file mode 100644 index 0000000..9a87e76 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletLarge.cs @@ -0,0 +1,24 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace AlienAttack.MonoGame.Things.Bullets; + +internal class MinigunBulletLarge : Bullet +{ + public MinigunBulletLarge(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner) + { + BoundBox = new(0, 0, 9, 27); + Damage = 3; + } + + public override void Draw(SpriteDrawArgs args) + { + Texture2D texture = args.Content.Load(@$"Sprites\Minigun_Large"); + + float rotation = MathF.Atan2(YVelocity, XVelocity) + MathF.PI / 2f; + Vector2 origin = new(texture.Width / 2f, texture.Height / 2f); + + args.SpriteBatch.Draw(texture, Position, null, DrawColor, rotation, origin, 1f, SpriteEffects.None, 1); + } +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Bullets/MinigunBulletMedium.cs b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletMedium.cs new file mode 100644 index 0000000..3b604d0 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletMedium.cs @@ -0,0 +1,24 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace AlienAttack.MonoGame.Things.Bullets; + +internal class MinigunBulletMedium : Bullet +{ + public MinigunBulletMedium(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner) + { + BoundBox = new(0, 0, 11, 21); + Damage = 2; + } + + public override void Draw(SpriteDrawArgs args) + { + Texture2D texture = args.Content.Load(@$"Sprites\Minigun_Medium"); + + float rotation = MathF.Atan2(YVelocity, XVelocity) + MathF.PI / 2f; + Vector2 origin = new(texture.Width / 2f, texture.Height / 2f); + + args.SpriteBatch.Draw(texture, Position, null, DrawColor, rotation, origin, 1f, SpriteEffects.None, 1); + } +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Bullets/MinigunBulletSmall.cs b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletSmall.cs index 62e3164..91c74ae 100644 --- a/AlientAttack.MonoGame/Things/Bullets/MinigunBulletSmall.cs +++ b/AlientAttack.MonoGame/Things/Bullets/MinigunBulletSmall.cs @@ -8,9 +8,7 @@ internal class MinigunBulletSmall : Bullet { public MinigunBulletSmall(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner) { - XVelocity = xVel; - YVelocity = yVel; - Owner = owner; + BoundBox = new(0, 0, 7, 17); Damage = 1; } diff --git a/AlientAttack.MonoGame/Things/Enemies/EnemyShip.cs b/AlientAttack.MonoGame/Things/Enemies/EnemyShip.cs new file mode 100644 index 0000000..3eee471 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Enemies/EnemyShip.cs @@ -0,0 +1,6 @@ +namespace AlienAttack.MonoGame.Things.Enemies; + +public abstract class EnemyShip(int x, int y) : MoveableSprite(x, y) +{ + public virtual int CrashDamage => 10; +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs index 53e99f3..f5a434d 100644 --- a/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs +++ b/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs @@ -9,7 +9,7 @@ using System.Collections.Generic; namespace AlienAttack.MonoGame.Things.Enemies; -internal class GreenEnemy : MoveableSprite +internal class GreenEnemy : EnemyShip { //Enemy01_Green_Frame_1_png_processed @@ -113,5 +113,10 @@ internal class GreenEnemy : MoveableSprite { Health -= bullet.Damage; } + + if (context.Sprite is Player) + { + Health = 0; + } } } \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs index 54e5f18..6c07e72 100644 --- a/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs +++ b/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs @@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics; namespace AlienAttack.MonoGame.Things.Enemies; -internal class RedEnemy : MoveableSprite +internal class RedEnemy : EnemyShip { protected int FireThreshold => 20; protected int CurrentFireThreshold { get; set; } = 20; @@ -92,5 +92,10 @@ internal class RedEnemy : MoveableSprite { Health -= bullet.Damage; } + + if (context.Sprite is Player) + { + Health = 0; + } } } \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs index acdb59c..e42c6d5 100644 --- a/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs +++ b/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs @@ -4,7 +4,7 @@ using Microsoft.Xna.Framework.Graphics; namespace AlienAttack.MonoGame.Things.Enemies; -internal class TealEnemy : MoveableSprite +internal class TealEnemy : EnemyShip { protected int FireThreshold => 20; protected int CurrentFireThreshold { get; set; } = 20; @@ -85,5 +85,10 @@ internal class TealEnemy : MoveableSprite { Health -= bullet.Damage; } + + if (context.Sprite is Player) + { + Health = 0; + } } } \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Player.cs b/AlientAttack.MonoGame/Things/Player.cs index 21f7f98..d085c42 100644 --- a/AlientAttack.MonoGame/Things/Player.cs +++ b/AlientAttack.MonoGame/Things/Player.cs @@ -1,9 +1,14 @@ -using AlienAttack.MonoGame.Things.Weapons; +using AlienAttack.MonoGame.Things.Bullets; +using AlienAttack.MonoGame.Things.Enemies; +using AlienAttack.MonoGame.Things.Items; +using AlienAttack.MonoGame.Things.Weapons; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; using System.Collections.Generic; +using System.IO; +using static System.Net.Mime.MediaTypeNames; namespace AlienAttack.MonoGame.Things; @@ -35,7 +40,6 @@ internal class Player : MoveableSprite protected PlayerHorizontalMoveState MoveState = PlayerHorizontalMoveState.None; protected MoveFlag MoveFlags; protected ulong MoveThreshold = 0; - //protected ulong FireThreshold = 0; protected ICollection ActiveWeapons = []; protected int CurrentExhaustFrame = 1; @@ -44,11 +48,16 @@ internal class Player : MoveableSprite protected int CurrentExhaustAnimationThreshold = 10; protected int CurrentExhaustDirection = 1; + public int Health { get; protected set; } + public int Shield { get; protected set; } + public Player(int x, int y) : base(x, y) { BoundBox = new Rectangle(0, 0, 64, 64); ActiveWeapons.Add(new Minigun()); - ActiveWeapons.Add(new FastMinigun()); + //ActiveWeapons.Add(new FastMinigun()); + Health = 100; + Shield = 100; } //Texture2D texture = Game @@ -244,12 +253,6 @@ internal class Player : MoveableSprite private void CheckFire(SpriteUpdateContext context) { - //if (FireThreshold > 0) - //{ - // FireThreshold--; - // return; - //} - foreach (IWeapon weapon in ActiveWeapons) { weapon.UpdateFireThreshold(); @@ -262,14 +265,6 @@ internal class Player : MoveableSprite { weapon.TryFire(this, context); } - - //MinigunBulletSmall bullet1 = new((int)Position.X + 12, (int)Position.Y + 6, 0, -4, this); - //MinigunBulletSmall bullet2 = new((int)Position.X + BoundBox.Width - 20, (int)Position.Y + 6, 0, -4, this); - - //context.SpawnSprite(bullet1); - //context.SpawnSprite(bullet2); - - //FireThreshold = 15; } private static bool IsFireButtonPressed() @@ -280,4 +275,68 @@ internal class Player : MoveableSprite return keyState.IsKeyDown(Keys.Space) || mouseState.LeftButton == ButtonState.Pressed || gamePadState.Buttons.A == ButtonState.Pressed; } + + private void TakeDamage(int amount) + { + if (Shield >= amount) + { + TakeShieldDamage(amount); + return; + } + + if (Shield > 0) + { + int remainder = amount - Shield; + + TakeShieldDamage(Shield); + TakeHealthDamage(remainder); + + return; + } + + TakeHealthDamage(amount); + } + + private void GiveShield(int amount) + { + Shield = Math.Clamp(Shield + amount, 0, 100); + } + + private void TakeShieldDamage(int amount) + { + Shield = Math.Clamp(Shield - amount, 0, 100); + } + + private void GiveHealth(int amount) + { + Health = Math.Clamp(Health + amount, 0, 100); + } + + private void TakeHealthDamage(int amount) + { + Health = Math.Clamp(Health - amount, 0, 100); + } + + public override void OnCollision(SpriteCollisionContext context) + { + if (context.Sprite is Bullet bullet && bullet.Owner is not Player) + { + TakeDamage(bullet.Damage); + } + + if (context.Sprite is EnemyShip enemyShip) + { + TakeDamage(enemyShip.CrashDamage); + } + + if (context.Sprite is Shields) + { + GiveShield(20); + } + + if (context.Sprite is Health) + { + GiveHealth(10); + } + } } \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Weapons/Minigun.cs b/AlientAttack.MonoGame/Things/Weapons/Minigun.cs index e4aeec9..78d4003 100644 --- a/AlientAttack.MonoGame/Things/Weapons/Minigun.cs +++ b/AlientAttack.MonoGame/Things/Weapons/Minigun.cs @@ -1,5 +1,4 @@ using AlienAttack.MonoGame.Things.Bullets; -using Microsoft.Xna.Framework; namespace AlienAttack.MonoGame.Things.Weapons; @@ -10,13 +9,13 @@ public class Minigun : Weapon public override void Fire(Sprite owner, SpriteUpdateContext context) { // Calculate bullet spawn positions relative to the player's bounding box - int x1 = (int)owner.XPosition + 12; - int x2 = (int)owner.XPosition + owner.BoundBox.Width - 20; - int y = (int)owner.YPosition + 6; + int x1 = (int)owner.XPosition + 14; + int x2 = (int)owner.XPosition + owner.BoundBox.Width - 16; + int y = (int)owner.YPosition + 10; // Create bullets with velocity (0, -4) - MinigunBulletSmall bullet1 = new(x1, y, 0, -6, owner); - MinigunBulletSmall bullet2 = new(x2, y, 0, -6, owner); + MinigunBulletLarge bullet1 = new(x1, y, 0, -6, owner); + MinigunBulletLarge bullet2 = new(x2, y, 0, -6, owner); // Queue the bullets for spawning context.SpawnSprite(bullet1);