using AlienAttack.MonoGame.Things; using AlienAttack.MonoGame.Things.Enemies; using AlienAttack.MonoGame.Things.Enemies.Mines; using AlienAttack.MonoGame.Things.Enemies.Turrets; using AlienAttack.MonoGame.Things.Stars; using AlienAttack.MonoGame.View; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; namespace AlienAttack.MonoGame.GameLoops; internal class GameLoop : GameLoopBase { private readonly List _spritesToAdd = []; private readonly Random _random = new(); private int _spawnNewEnemyThreshold = 100; private Starfield _starfield; private Texture2D _pixel; private Player _player; protected readonly List Sprites = []; public GameLoop(AlienAttackGame game) : base(game) { InitializeStarField(game); _player = new Player(game.ViewTransform.ScreenWidth / 2 - 32, game.ViewTransform.ScreenHeight - 64); Sprites.Add(_player); } private void InitializeStarField(AlienAttackGame game) { _starfield = new Starfield(seed: 42); //// Far layer: many tiny, slow, faint //_starfield.AddLayer(count: 450, speedPxPerSec: 20f, minSize: 1f, maxSize: 1.5f, minAlpha: 0.15f, maxAlpha: 0.35f); //// Mid layer //_starfield.AddLayer(count: 220, speedPxPerSec: 45f, minSize: 1.5f, maxSize: 2.2f, minAlpha: 0.25f, maxAlpha: 0.60f); //// Near layer: fewer, bigger, faster, brighter //_starfield.AddLayer(count: 90, speedPxPerSec: 85f, minSize: 2.0f, maxSize: 3.2f, minAlpha: 0.40f, maxAlpha: 0.90f); _starfield.AddLayer(count: 450, velocityPxPerSec: new Vector2(+4f, 20f), minSize: 1f, maxSize: 1.5f, minAlpha: 0.15f, maxAlpha: 0.35f); _starfield.AddLayer(count: 220, velocityPxPerSec: new Vector2(-10f, 45f), minSize: 1.5f, maxSize: 2.2f, minAlpha: 0.25f, maxAlpha: 0.60f); _starfield.AddLayer(count: 90, velocityPxPerSec: new Vector2(+20f, 85f), minSize: 2.0f, maxSize: 3.2f, minAlpha: 0.40f, maxAlpha: 0.90f); _pixel = new Texture2D(game.GraphicsDevice, 1, 1); _pixel.SetData(new[] { Color.White }); _starfield.Initialize(ViewTransform.ScreenWidth, ViewTransform.ScreenHeight); } public override void OnDraw(GameTime gameTime) { _starfield.Draw(SpriteBatch, _pixel); //DrawBackground(); DrawHud(gameTime); DrawSprites(); } private void DrawHud(GameTime gameTime) { double t = gameTime.TotalGameTime.TotalSeconds; DrawSideBars(SpriteBatch, ViewTransform.ScreenWidth, ViewTransform.ScreenHeight, _player.Health, _player.Shield, t); } private void DrawSideBars(SpriteBatch sb, int screenW, int screenH, int health, int shield, double totalSeconds) { health = Math.Clamp(health, 0, 100); shield = Math.Clamp(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); } } private void DrawSprites() { SpriteDrawArgs args = new(Game); foreach (Sprite sprite in Sprites) { sprite.Draw(args); } } public override void OnUpdate(GameTime gameTime) { _starfield.Update(gameTime); UpdateSprites(gameTime); CheckSpriteCollisions(); ClearDeadSprites(); AddEnemySprites(); AddNewSprites(); } private void UpdateSprites(GameTime gameTime) { SpriteUpdateContext args = new(Game) { Random = _random, GameTime = gameTime, Content = Content, SpawnSprite = _spritesToAdd.Add }; foreach (Sprite sprite in Sprites) { sprite.Update(args); } } private void CheckSpriteCollisions() { var collisionSprites = Sprites.Where(x => x.CanCollide).ToList(); foreach (var sprite in collisionSprites) { foreach (var otherSprite in collisionSprites.Where(x => x != sprite)) { SpriteCollisionContext context = new(Game) { Sprite = otherSprite, SpawnSprite = _spritesToAdd.Add, Random = _random, Content = Content }; // TODO: If perfomed once; do not perform again if (sprite.Intersects(otherSprite)) { sprite.OnCollision(context); } } } } private void ClearDeadSprites() { var deadSprites = Sprites.Where(x => x.IsDead).ToList(); foreach (var sprite in deadSprites) { Sprites.Remove(sprite); } } private void AddNewSprites() { foreach (Sprite sprite in _spritesToAdd) { Sprites.Add(sprite); } _spritesToAdd.Clear(); } private void AddEnemySprites() { if (_spawnNewEnemyThreshold > 0) { _spawnNewEnemyThreshold--; } if (_spawnNewEnemyThreshold == 0) { int randomNumber = _random.Next(0, 100); if (randomNumber == 0) { GreenEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 1) { RedEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 2) { TealEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 3) { Enemy02Green enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 4) { Enemy02Red enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 5) { Enemy02Teal enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 6) { GreenMine enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Mine.Width), -Mine.Height); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 7) { RedMine enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Mine.Width), -Mine.Height); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 8) { BlueMine enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Mine.Width), -Mine.Height); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 9) { GreenTurret enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Turret.MountWidth), -Turret.MountHeight); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 10) { RedTurret enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Turret.MountWidth), -Turret.MountHeight); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 11) { OrangeTurret enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Turret.MountWidth), -Turret.MountHeight); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } else if (randomNumber == 12) { TealTurret enemy = new(_random.Next(0, ViewTransform.ScreenWidth - Turret.MountWidth), -Turret.MountHeight); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } } } } public record DrawContext { public GameTime GameTime; public SpriteBatch SpriteBatch; public ViewTransform ViewTransoform; }