diff --git a/AlientAttack.MonoGame/GameLoops/GameLoop.cs b/AlientAttack.MonoGame/GameLoops/GameLoop.cs index 6f3d907..775f2c1 100644 --- a/AlientAttack.MonoGame/GameLoops/GameLoop.cs +++ b/AlientAttack.MonoGame/GameLoops/GameLoop.cs @@ -181,7 +181,7 @@ internal class GameLoop : GameLoopBase if (randomNumber == 0) { - Enemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); + GreenEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64); Sprites.Add(enemy); _spawnNewEnemyThreshold = 100 + _random.Next(0, 100); } @@ -191,6 +191,12 @@ internal class GameLoop : GameLoopBase 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); + } } } } \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Enemies/Enemy.cs b/AlientAttack.MonoGame/Things/Enemies/Enemy.cs index 5af8591..7f33c29 100644 --- a/AlientAttack.MonoGame/Things/Enemies/Enemy.cs +++ b/AlientAttack.MonoGame/Things/Enemies/Enemy.cs @@ -1,51 +1,33 @@ using AlienAttack.MonoGame.Things.Bullets; 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; namespace AlienAttack.MonoGame.Things.Enemies; -internal class Enemy : MoveableSprite +public abstract class Enemy(int x, int y) : MoveableSprite(x, y) { - //Enemy01_Green_Frame_1_png_processed - - protected ICollection ActiveWeapons = []; - protected int FireThreshold => 20; - protected int CurrentFireThreshold { get; set; } = 20; - protected int Health { get; set; } = 5; - - public Enemy(int x, int y) : base(x, y) - { - BoundBox = new Rectangle(0, 0, 64, 64); - YVelocity = 1; - ActiveWeapons.Add(new Minigun()); - //ActiveWeapons.Add(new FastMinigun()); - } - - public override void Draw(SpriteDrawArgs args) - { - Texture2D texture = args.Content.Load(@$"Sprites\Enemy01_Green_Frame_1_png_processed"); - SpriteEffects spriteEffects = SpriteEffects.None; - - args.SpriteBatch.Draw(texture, Position, null, DrawColor, 0, new Vector2(0, 0), 1, spriteEffects, 1); - - base.Draw(args); - } + protected abstract int Health { get; set; } public override void Update(SpriteUpdateContext context) { - //YPosition += 1; + CheckDeath(context); + + base.Update(context); + } + + private void CheckDeath(SpriteUpdateContext context) + { + if (YPosition > context.ViewTransform.ScreenHeight) + { + IsDead = true; + return; + } if (Health <= 0) { IsDead = true; context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity)); - switch (context.Random.Next(0, 4)) + switch (context.Random.Next(0, 5)) { case 0: context.SpawnSprite(new Health((int)XPosition, (int)YPosition)); @@ -66,45 +48,6 @@ internal class Enemy : MoveableSprite return; } - - if (YPosition > context.ViewTransform.ScreenHeight) - { - IsDead = true; - return; - } - - if (CurrentFireThreshold > 0) - { - CurrentFireThreshold--; - } - - if (CurrentFireThreshold == 0 && context.Random.Next(0, 100) == 1) - { - float originX = XPosition + (BoundBox.Width / 2) - (7 / 2); - - context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); - context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); - - CurrentFireThreshold = FireThreshold; - } - - //CheckMove(context); - CheckFire(context); - - base.Update(context); - } - - private void CheckFire(SpriteUpdateContext context) - { - //foreach (IWeapon weapon in ActiveWeapons) - //{ - // weapon.UpdateFireThreshold(); - //} - - //foreach (IWeapon weapon in ActiveWeapons) - //{ - // weapon.TryFire(this, context); - //} } public override void OnCollision(SpriteCollisionContext context) diff --git a/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs new file mode 100644 index 0000000..53e99f3 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Enemies/GreenEnemy.cs @@ -0,0 +1,117 @@ +using AlienAttack.MonoGame.Things.Bullets; +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; + +namespace AlienAttack.MonoGame.Things.Enemies; + +internal class GreenEnemy : MoveableSprite +{ + //Enemy01_Green_Frame_1_png_processed + + protected ICollection ActiveWeapons = []; + protected int FireThreshold => 20; + protected int CurrentFireThreshold { get; set; } = 20; + protected int Health { get; set; } = 5; + + public GreenEnemy(int x, int y) : base(x, y) + { + BoundBox = new Rectangle(0, 0, 64, 64); + YVelocity = 1; + ActiveWeapons.Add(new Minigun()); + //ActiveWeapons.Add(new FastMinigun()); + } + + public override void Draw(SpriteDrawArgs args) + { + Texture2D texture = args.Content.Load(@$"Sprites\Enemy01_Green_Frame_1_png_processed"); + SpriteEffects spriteEffects = SpriteEffects.None; + + args.SpriteBatch.Draw(texture, Position, null, DrawColor, 0, new Vector2(0, 0), 1, spriteEffects, 1); + + base.Draw(args); + } + + public override void Update(SpriteUpdateContext context) + { + //YPosition += 1; + + if (Health <= 0) + { + IsDead = true; + context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity)); + + switch (context.Random.Next(0, 5)) + { + case 0: + context.SpawnSprite(new Health((int)XPosition, (int)YPosition)); + break; + case 1: + context.SpawnSprite(new Shields((int)XPosition, (int)YPosition)); + break; + case 2: + context.SpawnSprite(new Ammo((int)XPosition, (int)YPosition)); + break; + case 3: + context.SpawnSprite(new Energy((int)XPosition, (int)YPosition)); + break; + case 4: + context.SpawnSprite(new Rockets((int)XPosition, (int)YPosition)); + break; + } + + return; + } + + if (YPosition > context.ViewTransform.ScreenHeight) + { + IsDead = true; + return; + } + + if (CurrentFireThreshold > 0) + { + CurrentFireThreshold--; + } + + if (CurrentFireThreshold == 0 && context.Random.Next(0, 100) == 1) + { + float originX = XPosition + (BoundBox.Width / 2) - (7 / 2); + + context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + + CurrentFireThreshold = FireThreshold; + } + + //CheckMove(context); + CheckFire(context); + + base.Update(context); + } + + private void CheckFire(SpriteUpdateContext context) + { + //foreach (IWeapon weapon in ActiveWeapons) + //{ + // weapon.UpdateFireThreshold(); + //} + + //foreach (IWeapon weapon in ActiveWeapons) + //{ + // weapon.TryFire(this, context); + //} + } + + public override void OnCollision(SpriteCollisionContext context) + { + if (context.Sprite is Bullet bullet && bullet.Owner is Player) + { + Health -= bullet.Damage; + } + } +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs index f0adefd..54e5f18 100644 --- a/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs +++ b/AlientAttack.MonoGame/Things/Enemies/RedEnemy.cs @@ -1,29 +1,27 @@ using AlienAttack.MonoGame.Things.Bullets; -using AlienAttack.MonoGame.Things.Weapons; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; namespace AlienAttack.MonoGame.Things.Enemies; internal class RedEnemy : MoveableSprite { - //Enemy01_Green_Frame_1_png_processed - - protected int Health { get; set; } = 10; + protected int FireThreshold => 20; + protected int CurrentFireThreshold { get; set; } = 20; + protected int Health { get; set; } = 5; public RedEnemy(int x, int y) : base(x, y) { BoundBox = new Rectangle(0, 0, 64, 64); YVelocity = 2; - //ActiveWeapons.Add(new Minigun()); - //ActiveWeapons.Add(new FastMinigun()); } public override void Draw(SpriteDrawArgs args) { - Texture2D texture = args.Content.Load(@$"Sprites\Enemy01_Red_Frame_1_png_processed"); - SpriteEffects spriteEffects = SpriteEffects.None; + int frame = XVelocity > 0 ? 2 : XVelocity < 0 ? 3 : 1; + SpriteEffects spriteEffects = XVelocity > 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None; + Texture2D texture = args.Content.Load(@$"Sprites\Enemy01_Red_Frame_{frame}_png_processed"); + //SpriteEffects spriteEffects = SpriteEffects.None; args.SpriteBatch.Draw(texture, Position, null, DrawColor, 0, new Vector2(0, 0), 1, spriteEffects, 1); @@ -33,6 +31,24 @@ internal class RedEnemy : MoveableSprite public override void Update(SpriteUpdateContext context) { //YPosition += 2; + //if (YPosition > context.ViewTransform.ScreenHeight / 5 - BoundBox.Y / 2) + //{ + // if (YPosition < context.ViewTransform.ScreenHeight / 2 - BoundBox.Y / 2) + // { + // if (XPosition < context.ViewTransform.ScreenWidth / 2) + // { + // XVelocity = 2; + // } + // else if (XPosition > context.ViewTransform.ScreenWidth / 2) + // { + // XVelocity = -2; + // } + // } + // else + // { + // XVelocity = 0; + // } + //} if (Health <= 0) { @@ -47,23 +63,27 @@ internal class RedEnemy : MoveableSprite return; } - //CheckMove(context); - CheckFire(context); + TryFire(context); base.Update(context); } - private void CheckFire(SpriteUpdateContext context) + private void TryFire(SpriteUpdateContext context) { - //foreach (IWeapon weapon in ActiveWeapons) - //{ - // weapon.UpdateFireThreshold(); - //} + if (CurrentFireThreshold > 0) + { + CurrentFireThreshold--; + } - //foreach (IWeapon weapon in ActiveWeapons) - //{ - // weapon.TryFire(this, context); - //} + if (CurrentFireThreshold == 0 && context.Random.Next(0, 20) == 1) + { + float originX = XPosition + (BoundBox.Width / 2) - (7 / 2); + + context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + + CurrentFireThreshold = FireThreshold; + } } public override void OnCollision(SpriteCollisionContext context) diff --git a/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs b/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs new file mode 100644 index 0000000..acdb59c --- /dev/null +++ b/AlientAttack.MonoGame/Things/Enemies/TealEnemy.cs @@ -0,0 +1,89 @@ +using AlienAttack.MonoGame.Things.Bullets; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace AlienAttack.MonoGame.Things.Enemies; + +internal class TealEnemy : MoveableSprite +{ + protected int FireThreshold => 20; + protected int CurrentFireThreshold { get; set; } = 20; + protected int Health { get; set; } = 5; + + public TealEnemy(int x, int y) : base(x, y) + { + BoundBox = new Rectangle(0, 0, 64, 64); + YVelocity = 1; + XVelocity = 3; + } + + public override void Draw(SpriteDrawArgs args) + { + int frame = XVelocity > 0 ? 2 : XVelocity < 0 ? 3 : 1; + SpriteEffects spriteEffects = XVelocity > 0 ? SpriteEffects.FlipHorizontally : SpriteEffects.None; + Texture2D texture = args.Content.Load(@$"Sprites\Enemy01_Teal_Frame_{frame}_png_processed"); + //SpriteEffects spriteEffects = SpriteEffects.None; + + args.SpriteBatch.Draw(texture, Position, null, DrawColor, 0, new Vector2(0, 0), 1, spriteEffects, 1); + + base.Draw(args); + } + + public override void Update(SpriteUpdateContext context) + { + if (XPosition + BoundBox.Width >= context.ViewTransform.ScreenWidth) + { + XPosition = context.ViewTransform.ScreenWidth - BoundBox.Width; + XVelocity *= -1; + } + else if (XPosition <= 0) + { + XPosition = 0; + XVelocity *= -1; + } + + if (Health <= 0) + { + IsDead = true; + context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity)); + return; + } + + if (YPosition > context.ViewTransform.ScreenHeight) + { + IsDead = true; + return; + } + + //CheckMove(context); + TryFire(context); + + base.Update(context); + } + + private void TryFire(SpriteUpdateContext context) + { + if (CurrentFireThreshold > 0) + { + CurrentFireThreshold--; + } + + if (CurrentFireThreshold == 0 && context.Random.Next(0, 50) == 1) + { + float originX = XPosition + (BoundBox.Width / 2) - (7 / 2); + + context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this)); + + CurrentFireThreshold = FireThreshold; + } + } + + public override void OnCollision(SpriteCollisionContext context) + { + if (context.Sprite is Bullet bullet && bullet.Owner is Player) + { + Health -= bullet.Damage; + } + } +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Items/Item.cs b/AlientAttack.MonoGame/Things/Items/Item.cs index 51a377f..3469a91 100644 --- a/AlientAttack.MonoGame/Things/Items/Item.cs +++ b/AlientAttack.MonoGame/Things/Items/Item.cs @@ -12,7 +12,7 @@ internal abstract class Item : MoveableSprite private float _omega = 2.2f; // radians/sec (speed of orbit) private float _scale = 1f; - protected string? TextureName; + protected string TextureName; public Item(int x, int y) : base(x, y) { diff --git a/AlientAttack.MonoGame/Things/Player.cs b/AlientAttack.MonoGame/Things/Player.cs index c8a4c96..21f7f98 100644 --- a/AlientAttack.MonoGame/Things/Player.cs +++ b/AlientAttack.MonoGame/Things/Player.cs @@ -21,7 +21,8 @@ internal enum MoveFlag Left = 1, Right = 2, Up = 4, - Down = 8 + Down = 8, + Boost = 16 } public class DrwaState @@ -184,6 +185,11 @@ internal class Player : MoveableSprite MoveThreshold = 0; } + if (MoveFlags.HasFlag(MoveFlag.Boost)) + { + XVelocity *= 1.5f; + } + if (XPosition < 0) { XPosition = 0; @@ -229,6 +235,11 @@ internal class Player : MoveableSprite { MoveFlags |= MoveFlag.Right; } + + if (gamepadState.Buttons.RightShoulder == ButtonState.Pressed) + { + MoveFlags |= MoveFlag.Boost; + } } private void CheckFire(SpriteUpdateContext context) diff --git a/AlientAttack.MonoGame/Things/Stars/Star.cs b/AlientAttack.MonoGame/Things/Stars/Star.cs new file mode 100644 index 0000000..97dd608 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Stars/Star.cs @@ -0,0 +1,13 @@ +using Microsoft.Xna.Framework; + +namespace AlienAttack.MonoGame.Things.Stars; + +internal struct Star +{ + public Vector2 Pos; + public float Size; + public float Alpha; + + public float TwinkleSpeed; + public float TwinkleT; +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Stars/StarLayer.cs b/AlientAttack.MonoGame/Things/Stars/StarLayer.cs new file mode 100644 index 0000000..e0bb817 --- /dev/null +++ b/AlientAttack.MonoGame/Things/Stars/StarLayer.cs @@ -0,0 +1,134 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; + +namespace AlienAttack.MonoGame.Things.Stars; + +internal sealed class StarLayer +{ + private readonly Random _rng; + private readonly int _count; + //private readonly float _speed; + private readonly Vector2 _velocity; // pixels/sec + private readonly float _minSize, _maxSize; + private readonly float _minAlpha, _maxAlpha; + + private int _w, _h; + private readonly Star[] _stars; + + public StarLayer(Random rng, int count, Vector2 velocityPxPerSec, float minSize, float maxSize, float minAlpha, float maxAlpha) + { + _rng = rng; + _count = count; + //_speed = speedPxPerSec; + _velocity = velocityPxPerSec; + _minSize = minSize; + _maxSize = maxSize; + _minAlpha = minAlpha; + _maxAlpha = maxAlpha; + + _stars = new Star[_count]; + } + + public void Initialize(int screenWidth, int screenHeight) + { + _w = screenWidth; + _h = screenHeight; + + for (int i = 0; i < _stars.Length; i++) + _stars[i] = CreateStar(randomY: true); + } + + public void OnResize(int screenWidth, int screenHeight) + { + // Keep existing stars but clamp/wrap them into the new bounds. + _w = screenWidth; + _h = screenHeight; + + for (int i = 0; i < _stars.Length; i++) + { + var s = _stars[i]; + s.Pos.X = Wrap(s.Pos.X, _w); + s.Pos.Y = Wrap(s.Pos.Y, _h); + _stars[i] = s; + } + } + + public void Update(float dt) + { + for (int i = 0; i < _stars.Length; i++) + { + var s = _stars[i]; + + // Move down (positive Y). Add slight horizontal drift if you want: + // s.Pos.X += s.DriftX * dt; + + //s.Pos.Y += _speed * dt; + s.Pos += _velocity * dt; + + // Wrap X continuously so diagonal drift never runs out of stars + if (s.Pos.X < -s.Size) s.Pos.X = _w + s.Size; + if (s.Pos.X > _w + s.Size) s.Pos.X = -s.Size; + + if (s.Pos.Y >= _h + s.Size) + { + // Respawn at top; keep it entering from above + s = CreateStar(randomY: false); + s.Pos.Y = -s.Size; + } + + // Optional subtle twinkle: + s.TwinkleT += dt * s.TwinkleSpeed; + + _stars[i] = s; + } + } + + public void Draw(SpriteBatch sb, Texture2D pixel) + { + for (int i = 0; i < _stars.Length; i++) + { + var s = _stars[i]; + + float twinkle = 1f; + if (s.TwinkleSpeed > 0f) + twinkle = 0.85f + 0.15f * (float)Math.Sin(s.TwinkleT); + + var color = Color.White * (s.Alpha * twinkle); + + // Draw as a scaled 1x1 pixel (super fast) + sb.Draw(pixel, s.Pos, null, color, 0f, Vector2.Zero, s.Size, SpriteEffects.None, 0f); + } + } + + private Star CreateStar(bool randomY) + { + float x = (float)_rng.NextDouble() * _w; + float y = randomY ? (float)_rng.NextDouble() * _h : 0f; + + float size = Lerp(_minSize, _maxSize, (float)_rng.NextDouble()); + float alpha = Lerp(_minAlpha, _maxAlpha, (float)_rng.NextDouble()); + + // Twinkle: keep it more common on “near” layers by setting a nonzero speed range + float twinkleSpeed = Lerp(0f, 6f, (float)_rng.NextDouble()) * 0.4f; // tweak or set 0 for none + + return new Star + { + Pos = new Vector2(x, y), + Size = size, + Alpha = alpha, + TwinkleSpeed = twinkleSpeed, + TwinkleT = (float)_rng.NextDouble() * MathF.PI * 2f + }; + } + + private static float Wrap(float v, float max) + { + if (max <= 0) return 0; + v %= max; + if (v < 0) v += max; + return v; + } + + private static float Lerp(float a, float b, float t) => a + (b - a) * t; +} \ No newline at end of file diff --git a/AlientAttack.MonoGame/Things/Stars/Starfield.cs b/AlientAttack.MonoGame/Things/Stars/Starfield.cs index bd4ac61..9d7e183 100644 --- a/AlientAttack.MonoGame/Things/Stars/Starfield.cs +++ b/AlientAttack.MonoGame/Things/Stars/Starfield.cs @@ -1,190 +1,42 @@ -using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace AlienAttack.MonoGame.Things.Stars +namespace AlienAttack.MonoGame.Things.Stars; + +internal sealed class Starfield(int seed = 12345) { - using Microsoft.Xna.Framework; - using Microsoft.Xna.Framework.Graphics; - using System; - using System.Collections.Generic; + private readonly List _layers = []; + private readonly Random _rng = new(seed); - public sealed class Starfield + public void AddLayer(int count, Vector2 velocityPxPerSec, float minSize, float maxSize, float minAlpha, float maxAlpha) { - private readonly List _layers = new(); - private readonly Random _rng; - - public Starfield(int seed = 12345) => _rng = new Random(seed); - - public void AddLayer(int count, Vector2 velocityPxPerSec, float minSize, float maxSize, float minAlpha, float maxAlpha) - { - _layers.Add(new StarLayer(_rng, count, velocityPxPerSec, minSize, maxSize, minAlpha, maxAlpha)); - } - - public void Initialize(int screenWidth, int screenHeight) - { - foreach (var layer in _layers) - layer.Initialize(screenWidth, screenHeight); - } - - public void OnResize(int screenWidth, int screenHeight) - { - foreach (var layer in _layers) - layer.OnResize(screenWidth, screenHeight); - } - - public void Update(GameTime gameTime) - { - float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; - foreach (var layer in _layers) - layer.Update(dt); - } - - public void Draw(SpriteBatch spriteBatch, Texture2D pixel) - { - foreach (var layer in _layers) - layer.Draw(spriteBatch, pixel); - } - - private sealed class StarLayer - { - private readonly Random _rng; - private readonly int _count; - //private readonly float _speed; - private readonly Vector2 _velocity; // pixels/sec - private readonly float _minSize, _maxSize; - private readonly float _minAlpha, _maxAlpha; - - private int _w, _h; - private readonly Star[] _stars; - - public StarLayer(Random rng, int count, Vector2 velocityPxPerSec, float minSize, float maxSize, float minAlpha, float maxAlpha) - { - _rng = rng; - _count = count; - //_speed = speedPxPerSec; - _velocity = velocityPxPerSec; - _minSize = minSize; - _maxSize = maxSize; - _minAlpha = minAlpha; - _maxAlpha = maxAlpha; - - _stars = new Star[_count]; - } - - public void Initialize(int screenWidth, int screenHeight) - { - _w = screenWidth; - _h = screenHeight; - - for (int i = 0; i < _stars.Length; i++) - _stars[i] = CreateStar(randomY: true); - } - - public void OnResize(int screenWidth, int screenHeight) - { - // Keep existing stars but clamp/wrap them into the new bounds. - _w = screenWidth; - _h = screenHeight; - - for (int i = 0; i < _stars.Length; i++) - { - var s = _stars[i]; - s.Pos.X = Wrap(s.Pos.X, _w); - s.Pos.Y = Wrap(s.Pos.Y, _h); - _stars[i] = s; - } - } - - public void Update(float dt) - { - for (int i = 0; i < _stars.Length; i++) - { - var s = _stars[i]; - - // Move down (positive Y). Add slight horizontal drift if you want: - // s.Pos.X += s.DriftX * dt; - - //s.Pos.Y += _speed * dt; - s.Pos += _velocity * dt; - - // Wrap X continuously so diagonal drift never runs out of stars - if (s.Pos.X < -s.Size) s.Pos.X = _w + s.Size; - if (s.Pos.X > _w + s.Size) s.Pos.X = -s.Size; - - if (s.Pos.Y >= _h + s.Size) - { - // Respawn at top; keep it entering from above - s = CreateStar(randomY: false); - s.Pos.Y = -s.Size; - } - - // Optional subtle twinkle: - s.TwinkleT += dt * s.TwinkleSpeed; - - _stars[i] = s; - } - } - - public void Draw(SpriteBatch sb, Texture2D pixel) - { - for (int i = 0; i < _stars.Length; i++) - { - var s = _stars[i]; - - float twinkle = 1f; - if (s.TwinkleSpeed > 0f) - twinkle = 0.85f + 0.15f * (float)Math.Sin(s.TwinkleT); - - var color = Color.White * (s.Alpha * twinkle); - - // Draw as a scaled 1x1 pixel (super fast) - sb.Draw(pixel, s.Pos, null, color, 0f, Vector2.Zero, s.Size, SpriteEffects.None, 0f); - } - } - - private Star CreateStar(bool randomY) - { - float x = (float)_rng.NextDouble() * _w; - float y = randomY ? (float)_rng.NextDouble() * _h : 0f; - - float size = Lerp(_minSize, _maxSize, (float)_rng.NextDouble()); - float alpha = Lerp(_minAlpha, _maxAlpha, (float)_rng.NextDouble()); - - // Twinkle: keep it more common on “near” layers by setting a nonzero speed range - float twinkleSpeed = Lerp(0f, 6f, (float)_rng.NextDouble()) * 0.4f; // tweak or set 0 for none - - return new Star - { - Pos = new Vector2(x, y), - Size = size, - Alpha = alpha, - TwinkleSpeed = twinkleSpeed, - TwinkleT = (float)_rng.NextDouble() * MathF.PI * 2f - }; - } - - private static float Wrap(float v, float max) - { - if (max <= 0) return 0; - v %= max; - if (v < 0) v += max; - return v; - } - - private static float Lerp(float a, float b, float t) => a + (b - a) * t; - - private struct Star - { - public Vector2 Pos; - public float Size; - public float Alpha; - - public float TwinkleSpeed; - public float TwinkleT; - } - } + _layers.Add(new StarLayer(_rng, count, velocityPxPerSec, minSize, maxSize, minAlpha, maxAlpha)); } -} + + public void Initialize(int screenWidth, int screenHeight) + { + foreach (var layer in _layers) + layer.Initialize(screenWidth, screenHeight); + } + + public void OnResize(int screenWidth, int screenHeight) + { + foreach (var layer in _layers) + layer.OnResize(screenWidth, screenHeight); + } + + public void Update(GameTime gameTime) + { + float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; + foreach (var layer in _layers) + layer.Update(dt); + } + + public void Draw(SpriteBatch spriteBatch, Texture2D pixel) + { + foreach (var layer in _layers) + layer.Draw(spriteBatch, pixel); + } +} \ No newline at end of file