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; 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)); } 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(); 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 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); } } } }