using AlienAttack.MonoGame.Things; using AlienAttack.MonoGame.Things.Enemies; using AlienAttack.MonoGame.Things.Stars; 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 float _backgroundYOffset = 0; 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() { _starfield.Draw(SpriteBatch, _pixel); //DrawBackground(); DrawHud(); DrawSprites(); } private void DrawBackground() { Texture2D backgroundTexture = Content.Load($@"Background\PixelBackgroundSeamlessVertically"); int blockCountY = (int)Math.Ceiling((decimal)ViewTransform.ScreenHeight / (decimal)backgroundTexture.Height); int blockCountX = (int)Math.Ceiling((decimal)ViewTransform.ScreenWidth / (decimal)backgroundTexture.Width); for (int y = 0; y < blockCountY; y++) { float startY = (y * backgroundTexture.Height) + _backgroundYOffset; _backgroundYOffset = _backgroundYOffset + 0.25f; if (_backgroundYOffset > ViewTransform.ScreenHeight) { _backgroundYOffset = 0; } for (int x = 0; x < blockCountX; x++) { int width = backgroundTexture.Width; if (x * backgroundTexture.Width + backgroundTexture.Width > ViewTransform.ScreenWidth) { width = ViewTransform.ScreenWidth - (x * backgroundTexture.Width); } Vector2 position = new(x * backgroundTexture.Width, startY); SpriteBatch.Draw(backgroundTexture, position, new Rectangle(new Point(x, y), new Point(width, backgroundTexture.Height)), Color.White); } } } 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); 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, 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 }; // 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); } } } }